mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
refactor(cli): migrate runtime compile/bundle to new infrastructure (#8192)
Fixes #8060
This commit is contained in:
parent
3558769d46
commit
fdcc78500c
23 changed files with 852 additions and 2770 deletions
|
@ -2,9 +2,11 @@
|
|||
|
||||
use crate::colors;
|
||||
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Deserializer;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde::Serializer;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -157,6 +159,21 @@ impl<'de> Deserialize<'de> for DiagnosticCategory {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for DiagnosticCategory {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let value = match self {
|
||||
DiagnosticCategory::Warning => 0 as i32,
|
||||
DiagnosticCategory::Error => 1 as i32,
|
||||
DiagnosticCategory::Suggestion => 2 as i32,
|
||||
DiagnosticCategory::Message => 3 as i32,
|
||||
};
|
||||
Serialize::serialize(&value, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for DiagnosticCategory {
|
||||
fn from(value: i64) -> Self {
|
||||
match value {
|
||||
|
@ -169,7 +186,7 @@ impl From<i64> for DiagnosticCategory {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DiagnosticMessageChain {
|
||||
message_text: String,
|
||||
|
@ -196,14 +213,14 @@ impl DiagnosticMessageChain {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Position {
|
||||
pub line: u64,
|
||||
pub character: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Diagnostic {
|
||||
pub category: DiagnosticCategory,
|
||||
|
@ -367,6 +384,15 @@ impl<'de> Deserialize<'de> for Diagnostics {
|
|||
}
|
||||
}
|
||||
|
||||
impl Serialize for Diagnostics {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
Serialize::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Diagnostics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut i = 0;
|
||||
|
|
27
cli/dts/lib.deno.unstable.d.ts
vendored
27
cli/dts/lib.deno.unstable.d.ts
vendored
|
@ -303,9 +303,6 @@ declare namespace Deno {
|
|||
/** Provide full support for iterables in `for..of`, spread and
|
||||
* destructuring when targeting ES5 or ES3. Defaults to `false`. */
|
||||
downlevelIteration?: boolean;
|
||||
/** Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
|
||||
* Defaults to `false`. */
|
||||
emitBOM?: boolean;
|
||||
/** Only emit `.d.ts` declaration files. Defaults to `false`. */
|
||||
emitDeclarationOnly?: boolean;
|
||||
/** Emit design-type metadata for decorated declarations in source. See issue
|
||||
|
@ -316,8 +313,11 @@ declare namespace Deno {
|
|||
* ecosystem compatibility and enable `allowSyntheticDefaultImports` for type
|
||||
* system compatibility. Defaults to `true`. */
|
||||
esModuleInterop?: boolean;
|
||||
/** Enables experimental support for ES decorators. Defaults to `false`. */
|
||||
/** Enables experimental support for ES decorators. Defaults to `true`. */
|
||||
experimentalDecorators?: boolean;
|
||||
/** Import emit helpers (e.g. `__extends`, `__rest`, etc..) from
|
||||
* [tslib](https://www.npmjs.com/package/tslib). */
|
||||
importHelpers?: boolean;
|
||||
/** Emit a single file with source maps instead of having a separate file.
|
||||
* Defaults to `false`. */
|
||||
inlineSourceMap?: boolean;
|
||||
|
@ -325,7 +325,7 @@ declare namespace Deno {
|
|||
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
|
||||
inlineSources?: boolean;
|
||||
/** Perform additional checks to ensure that transpile only would be safe.
|
||||
* Defaults to `false`. */
|
||||
* Defaults to `true`. */
|
||||
isolatedModules?: boolean;
|
||||
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
|
||||
* Defaults to `"react"`. */
|
||||
|
@ -333,12 +333,12 @@ declare namespace Deno {
|
|||
/** Specify the JSX factory function to use when targeting react JSX emit,
|
||||
* e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */
|
||||
jsxFactory?: string;
|
||||
/** Specify the JSX fragment factory function to use when targeting react
|
||||
* JSX emit, e.g. `Fragment`. Defaults to `React.Fragment`. */
|
||||
jsxFragmentFactory?: string;
|
||||
/** Resolve keyof to string valued property names only (no numbers or
|
||||
* symbols). Defaults to `false`. */
|
||||
keyofStringsOnly?: string;
|
||||
/** Emit class fields with ECMAScript-standard semantics. Defaults to `false`.
|
||||
*/
|
||||
useDefineForClassFields?: boolean;
|
||||
/** List of library files to be included in the compilation. If omitted,
|
||||
* then the Deno main runtime libs are used. */
|
||||
lib?: string[];
|
||||
|
@ -389,10 +389,6 @@ declare namespace Deno {
|
|||
noUnusedLocals?: boolean;
|
||||
/** Report errors on unused parameters. Defaults to `false`. */
|
||||
noUnusedParameters?: boolean;
|
||||
/** Redirect output structure to the directory. This only impacts
|
||||
* `Deno.compile` and only changes the emitted file names. Defaults to
|
||||
* `undefined`. */
|
||||
outDir?: string;
|
||||
/** List of path mapping entries for module names to locations relative to the
|
||||
* `baseUrl`. Defaults to `undefined`. */
|
||||
paths?: Record<string, string[]>;
|
||||
|
@ -402,8 +398,6 @@ declare namespace Deno {
|
|||
/** Remove all comments except copy-right header comments beginning with
|
||||
* `/*!`. Defaults to `true`. */
|
||||
removeComments?: boolean;
|
||||
/** Include modules imported with `.json` extension. Defaults to `true`. */
|
||||
resolveJsonModule?: boolean;
|
||||
/** Specifies the root directory of input files. Only use to control the
|
||||
* output directory structure with `outDir`. Defaults to `undefined`. */
|
||||
rootDir?: string;
|
||||
|
@ -418,6 +412,8 @@ declare namespace Deno {
|
|||
* specified will be embedded in the sourceMap to direct the debugger where
|
||||
* the source files will be located. Defaults to `undefined`. */
|
||||
sourceRoot?: string;
|
||||
/** Skip type checking of all declaration files (`*.d.ts`). */
|
||||
skipLibCheck?: boolean;
|
||||
/** Enable all strict type checking options. Enabling `strict` enables
|
||||
* `noImplicitAny`, `noImplicitThis`, `alwaysStrict`, `strictBindCallApply`,
|
||||
* `strictNullChecks`, `strictFunctionTypes` and
|
||||
|
@ -472,6 +468,9 @@ declare namespace Deno {
|
|||
* ```
|
||||
*/
|
||||
types?: string[];
|
||||
/** Emit class fields with ECMAScript-standard semantics. Defaults to
|
||||
* `false`. */
|
||||
useDefineForClassFields?: boolean;
|
||||
}
|
||||
|
||||
/** **UNSTABLE**: new API, yet to be vetted.
|
||||
|
|
32
cli/main.rs
32
cli/main.rs
|
@ -35,7 +35,6 @@ mod lint;
|
|||
mod lockfile;
|
||||
mod media_type;
|
||||
mod metrics;
|
||||
mod module_graph;
|
||||
mod module_graph2;
|
||||
mod module_loader;
|
||||
mod op_fetch_asset;
|
||||
|
@ -50,7 +49,6 @@ mod specifier_handler;
|
|||
mod test_runner;
|
||||
mod text_encoding;
|
||||
mod tokio_util;
|
||||
mod tsc;
|
||||
mod tsc2;
|
||||
mod tsc_config;
|
||||
mod upgrade;
|
||||
|
@ -242,6 +240,11 @@ async fn cache_command(
|
|||
flags: Flags,
|
||||
files: Vec<String>,
|
||||
) -> Result<(), AnyError> {
|
||||
let lib = if flags.unstable {
|
||||
module_graph2::TypeLib::UnstableDenoWindow
|
||||
} else {
|
||||
module_graph2::TypeLib::DenoWindow
|
||||
};
|
||||
let program_state = ProgramState::new(flags)?;
|
||||
|
||||
for file in files {
|
||||
|
@ -249,7 +252,7 @@ async fn cache_command(
|
|||
program_state
|
||||
.prepare_module_load(
|
||||
specifier,
|
||||
tsc::TargetLib::Main,
|
||||
lib.clone(),
|
||||
Permissions::allow_all(),
|
||||
false,
|
||||
program_state.maybe_import_map.clone(),
|
||||
|
@ -343,21 +346,20 @@ async fn bundle_command(
|
|||
module_graph2::TypeLib::DenoWindow
|
||||
};
|
||||
let graph = graph.clone();
|
||||
let (stats, diagnostics, maybe_ignored_options) =
|
||||
graph.check(module_graph2::CheckOptions {
|
||||
debug,
|
||||
emit: false,
|
||||
lib,
|
||||
maybe_config_path: flags.config_path.clone(),
|
||||
reload: flags.reload,
|
||||
})?;
|
||||
let result_info = graph.check(module_graph2::CheckOptions {
|
||||
debug,
|
||||
emit: false,
|
||||
lib,
|
||||
maybe_config_path: flags.config_path.clone(),
|
||||
reload: flags.reload,
|
||||
})?;
|
||||
|
||||
debug!("{}", stats);
|
||||
if let Some(ignored_options) = maybe_ignored_options {
|
||||
debug!("{}", result_info.stats);
|
||||
if let Some(ignored_options) = result_info.maybe_ignored_options {
|
||||
eprintln!("{}", ignored_options);
|
||||
}
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(generic_error(diagnostics.to_string()));
|
||||
if !result_info.diagnostics.is_empty() {
|
||||
return Err(generic_error(result_info.diagnostics.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::ModuleSpecifier;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use std::fmt;
|
||||
|
@ -60,6 +61,22 @@ impl<'a> From<&'a String> for MediaType {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ModuleSpecifier> for MediaType {
|
||||
fn from(specifier: &'a ModuleSpecifier) -> Self {
|
||||
let url = specifier.as_url();
|
||||
let path = if url.scheme() == "file" {
|
||||
if let Ok(path) = url.to_file_path() {
|
||||
path
|
||||
} else {
|
||||
PathBuf::from(url.path())
|
||||
}
|
||||
} else {
|
||||
PathBuf::from(url.path())
|
||||
};
|
||||
MediaType::from_path(&path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MediaType {
|
||||
fn default() -> Self {
|
||||
MediaType::Unknown
|
||||
|
|
|
@ -1,968 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::ast::Location;
|
||||
use crate::checksum;
|
||||
use crate::file_fetcher::SourceFile;
|
||||
use crate::file_fetcher::SourceFileFetcher;
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::media_type::MediaType;
|
||||
use crate::permissions::Permissions;
|
||||
use crate::tsc::pre_process_file;
|
||||
use crate::tsc::ImportDesc;
|
||||
use crate::tsc::TsReferenceDesc;
|
||||
use crate::tsc::TsReferenceKind;
|
||||
use crate::tsc::AVAILABLE_LIBS;
|
||||
use crate::version;
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::stream::FuturesUnordered;
|
||||
use deno_core::futures::stream::StreamExt;
|
||||
use deno_core::futures::Future;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::pin::Pin;
|
||||
|
||||
// TODO(bartlomieju): it'd be great if this function returned
|
||||
// more structured data and possibly format the same as TS diagnostics.
|
||||
/// Decorate error with location of import that caused the error.
|
||||
fn err_with_location(
|
||||
e: AnyError,
|
||||
maybe_location: Option<&Location>,
|
||||
) -> AnyError {
|
||||
if let Some(location) = maybe_location {
|
||||
let location_str = format!(
|
||||
"\nImported from \"{}:{}\"",
|
||||
location.filename, location.line
|
||||
);
|
||||
let err_str = e.to_string();
|
||||
generic_error(format!("{}{}", err_str, location_str))
|
||||
} else {
|
||||
e
|
||||
}
|
||||
}
|
||||
|
||||
/// Disallow http:// imports from modules loaded over https://
|
||||
fn validate_no_downgrade(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<&ModuleSpecifier>,
|
||||
maybe_location: Option<&Location>,
|
||||
) -> Result<(), AnyError> {
|
||||
if let Some(referrer) = maybe_referrer.as_ref() {
|
||||
if let "https" = referrer.as_url().scheme() {
|
||||
if let "http" = module_specifier.as_url().scheme() {
|
||||
let e = custom_error("PermissionDenied",
|
||||
"Modules loaded over https:// are not allowed to import modules over http://"
|
||||
);
|
||||
return Err(err_with_location(e, maybe_location));
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that remote file doesn't try to statically import local file.
|
||||
fn validate_no_file_from_remote(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<&ModuleSpecifier>,
|
||||
maybe_location: Option<&Location>,
|
||||
) -> Result<(), AnyError> {
|
||||
if let Some(referrer) = maybe_referrer.as_ref() {
|
||||
let referrer_url = referrer.as_url();
|
||||
match referrer_url.scheme() {
|
||||
"http" | "https" => {
|
||||
let specifier_url = module_specifier.as_url();
|
||||
match specifier_url.scheme() {
|
||||
"http" | "https" => {}
|
||||
_ => {
|
||||
let e = custom_error(
|
||||
"PermissionDenied",
|
||||
"Remote modules are not allowed to statically import local \
|
||||
modules. Use dynamic import instead.",
|
||||
);
|
||||
return Err(err_with_location(e, maybe_location));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): handle imports/references in ambient contexts/TS modules
|
||||
// https://github.com/denoland/deno/issues/6133
|
||||
fn resolve_imports_and_references(
|
||||
referrer: ModuleSpecifier,
|
||||
maybe_import_map: Option<&ImportMap>,
|
||||
import_descs: Vec<ImportDesc>,
|
||||
ref_descs: Vec<TsReferenceDesc>,
|
||||
) -> Result<(Vec<ImportDescriptor>, Vec<ReferenceDescriptor>), AnyError> {
|
||||
let mut imports = vec![];
|
||||
let mut references = vec![];
|
||||
|
||||
for import_desc in import_descs {
|
||||
let maybe_resolved = if let Some(import_map) = maybe_import_map.as_ref() {
|
||||
import_map.resolve(&import_desc.specifier, &referrer.to_string())?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let resolved_specifier = if let Some(resolved) = maybe_resolved {
|
||||
resolved
|
||||
} else {
|
||||
ModuleSpecifier::resolve_import(
|
||||
&import_desc.specifier,
|
||||
&referrer.to_string(),
|
||||
)?
|
||||
};
|
||||
|
||||
let resolved_type_directive =
|
||||
if let Some(types_specifier) = import_desc.deno_types.as_ref() {
|
||||
Some(ModuleSpecifier::resolve_import(
|
||||
&types_specifier,
|
||||
&referrer.to_string(),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let import_descriptor = ImportDescriptor {
|
||||
specifier: import_desc.specifier.to_string(),
|
||||
resolved_specifier,
|
||||
type_directive: import_desc.deno_types.clone(),
|
||||
resolved_type_directive,
|
||||
location: import_desc.location,
|
||||
};
|
||||
|
||||
imports.push(import_descriptor);
|
||||
}
|
||||
|
||||
for ref_desc in ref_descs {
|
||||
if AVAILABLE_LIBS.contains(&ref_desc.specifier.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let resolved_specifier = ModuleSpecifier::resolve_import(
|
||||
&ref_desc.specifier,
|
||||
&referrer.to_string(),
|
||||
)?;
|
||||
|
||||
let reference_descriptor = ReferenceDescriptor {
|
||||
specifier: ref_desc.specifier.to_string(),
|
||||
resolved_specifier,
|
||||
kind: ref_desc.kind,
|
||||
location: ref_desc.location,
|
||||
};
|
||||
|
||||
references.push(reference_descriptor);
|
||||
}
|
||||
|
||||
Ok((imports, references))
|
||||
}
|
||||
|
||||
fn serialize_module_specifier<S>(
|
||||
spec: &ModuleSpecifier,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
s.serialize_str(&spec.to_string())
|
||||
}
|
||||
|
||||
fn serialize_option_module_specifier<S>(
|
||||
maybe_spec: &Option<ModuleSpecifier>,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(spec) = maybe_spec {
|
||||
serialize_module_specifier(spec, s)
|
||||
} else {
|
||||
s.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
const SUPPORTED_MEDIA_TYPES: [MediaType; 4] = [
|
||||
MediaType::JavaScript,
|
||||
MediaType::TypeScript,
|
||||
MediaType::JSX,
|
||||
MediaType::TSX,
|
||||
];
|
||||
|
||||
pub type ModuleGraph = HashMap<String, ModuleGraphFile>;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImportDescriptor {
|
||||
pub specifier: String,
|
||||
#[serde(serialize_with = "serialize_module_specifier")]
|
||||
pub resolved_specifier: ModuleSpecifier,
|
||||
// These two fields are for support of @deno-types directive
|
||||
// directly prepending import statement
|
||||
pub type_directive: Option<String>,
|
||||
#[serde(serialize_with = "serialize_option_module_specifier")]
|
||||
pub resolved_type_directive: Option<ModuleSpecifier>,
|
||||
#[serde(skip)]
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReferenceDescriptor {
|
||||
pub specifier: String,
|
||||
#[serde(serialize_with = "serialize_module_specifier")]
|
||||
pub resolved_specifier: ModuleSpecifier,
|
||||
#[serde(skip)]
|
||||
pub kind: TsReferenceKind,
|
||||
#[serde(skip)]
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ModuleGraphFile {
|
||||
pub specifier: String,
|
||||
pub url: String,
|
||||
pub redirect: Option<String>,
|
||||
pub filename: String,
|
||||
pub version_hash: String,
|
||||
pub imports: Vec<ImportDescriptor>,
|
||||
pub referenced_files: Vec<ReferenceDescriptor>,
|
||||
pub lib_directives: Vec<ReferenceDescriptor>,
|
||||
pub types_directives: Vec<ReferenceDescriptor>,
|
||||
pub type_headers: Vec<ReferenceDescriptor>,
|
||||
pub media_type: MediaType,
|
||||
pub source_code: String,
|
||||
}
|
||||
|
||||
type SourceFileFuture = Pin<
|
||||
Box<dyn Future<Output = Result<(ModuleSpecifier, SourceFile), AnyError>>>,
|
||||
>;
|
||||
|
||||
pub struct ModuleGraphLoader {
|
||||
permissions: Permissions,
|
||||
file_fetcher: SourceFileFetcher,
|
||||
maybe_import_map: Option<ImportMap>,
|
||||
pending_downloads: FuturesUnordered<SourceFileFuture>,
|
||||
has_downloaded: HashSet<ModuleSpecifier>,
|
||||
graph: ModuleGraph,
|
||||
is_dyn_import: bool,
|
||||
analyze_dynamic_imports: bool,
|
||||
}
|
||||
|
||||
impl ModuleGraphLoader {
|
||||
pub fn new(
|
||||
file_fetcher: SourceFileFetcher,
|
||||
maybe_import_map: Option<ImportMap>,
|
||||
permissions: Permissions,
|
||||
is_dyn_import: bool,
|
||||
analyze_dynamic_imports: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
file_fetcher,
|
||||
permissions,
|
||||
maybe_import_map,
|
||||
pending_downloads: FuturesUnordered::new(),
|
||||
has_downloaded: HashSet::new(),
|
||||
graph: ModuleGraph::new(),
|
||||
is_dyn_import,
|
||||
analyze_dynamic_imports,
|
||||
}
|
||||
}
|
||||
|
||||
/// This method is used to add specified module and all of its
|
||||
/// dependencies to the graph.
|
||||
///
|
||||
/// It resolves when all dependent modules have been fetched and analyzed.
|
||||
///
|
||||
/// This method can be called multiple times.
|
||||
pub async fn add_to_graph(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
maybe_referrer: Option<ModuleSpecifier>,
|
||||
) -> Result<(), AnyError> {
|
||||
self.download_module(specifier.clone(), maybe_referrer, None)?;
|
||||
|
||||
loop {
|
||||
let (specifier, source_file) =
|
||||
self.pending_downloads.next().await.unwrap()?;
|
||||
self.visit_module(&specifier, source_file)?;
|
||||
if self.pending_downloads.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This method is used to create a graph from in-memory files stored in
|
||||
/// a hash map. Useful for creating module graph for code received from
|
||||
/// the runtime.
|
||||
pub fn build_local_graph(
|
||||
&mut self,
|
||||
_root_name: &str,
|
||||
source_map: &HashMap<String, String>,
|
||||
) -> Result<(), AnyError> {
|
||||
for (spec, source_code) in source_map.iter() {
|
||||
self.visit_memory_module(spec.to_string(), source_code.to_string())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Consumes the loader and returns created graph.
|
||||
pub fn get_graph(self) -> ModuleGraph {
|
||||
self.graph
|
||||
}
|
||||
|
||||
fn visit_memory_module(
|
||||
&mut self,
|
||||
specifier: String,
|
||||
source_code: String,
|
||||
) -> Result<(), AnyError> {
|
||||
let mut referenced_files = vec![];
|
||||
let mut lib_directives = vec![];
|
||||
let mut types_directives = vec![];
|
||||
|
||||
// FIXME(bartlomieju):
|
||||
// The resolveModules op only handles fully qualified URLs for referrer.
|
||||
// However we will have cases where referrer is "/foo.ts". We add this dummy
|
||||
// prefix "memory://" in order to use resolution logic.
|
||||
let module_specifier =
|
||||
if let Ok(spec) = ModuleSpecifier::resolve_url(&specifier) {
|
||||
spec
|
||||
} else {
|
||||
ModuleSpecifier::resolve_url(&format!("memory://{}", specifier))?
|
||||
};
|
||||
|
||||
let (raw_imports, raw_references) = pre_process_file(
|
||||
&module_specifier.to_string(),
|
||||
MediaType::from(&specifier),
|
||||
&source_code,
|
||||
self.analyze_dynamic_imports,
|
||||
)?;
|
||||
let (imports, references) = resolve_imports_and_references(
|
||||
module_specifier.clone(),
|
||||
self.maybe_import_map.as_ref(),
|
||||
raw_imports,
|
||||
raw_references,
|
||||
)?;
|
||||
|
||||
for ref_descriptor in references {
|
||||
match ref_descriptor.kind {
|
||||
TsReferenceKind::Lib => {
|
||||
lib_directives.push(ref_descriptor);
|
||||
}
|
||||
TsReferenceKind::Types => {
|
||||
types_directives.push(ref_descriptor);
|
||||
}
|
||||
TsReferenceKind::Path => {
|
||||
referenced_files.push(ref_descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.graph.insert(
|
||||
module_specifier.to_string(),
|
||||
ModuleGraphFile {
|
||||
specifier: specifier.to_string(),
|
||||
url: specifier.to_string(),
|
||||
redirect: None,
|
||||
version_hash: "".to_string(),
|
||||
media_type: MediaType::from(&specifier),
|
||||
filename: specifier,
|
||||
source_code,
|
||||
imports,
|
||||
referenced_files,
|
||||
lib_directives,
|
||||
types_directives,
|
||||
type_headers: vec![],
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): decorate errors with import location in the source code
|
||||
// https://github.com/denoland/deno/issues/5080
|
||||
fn download_module(
|
||||
&mut self,
|
||||
module_specifier: ModuleSpecifier,
|
||||
maybe_referrer: Option<ModuleSpecifier>,
|
||||
maybe_location: Option<Location>,
|
||||
) -> Result<(), AnyError> {
|
||||
if self.has_downloaded.contains(&module_specifier) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
validate_no_downgrade(
|
||||
&module_specifier,
|
||||
maybe_referrer.as_ref(),
|
||||
maybe_location.as_ref(),
|
||||
)?;
|
||||
|
||||
if !self.is_dyn_import {
|
||||
validate_no_file_from_remote(
|
||||
&module_specifier,
|
||||
maybe_referrer.as_ref(),
|
||||
maybe_location.as_ref(),
|
||||
)?;
|
||||
}
|
||||
|
||||
self.has_downloaded.insert(module_specifier.clone());
|
||||
let spec = module_specifier;
|
||||
let file_fetcher = self.file_fetcher.clone();
|
||||
let perms = self.permissions.clone();
|
||||
|
||||
let load_future = async move {
|
||||
let spec_ = spec.clone();
|
||||
let source_file = file_fetcher
|
||||
.fetch_source_file(&spec_, maybe_referrer, perms)
|
||||
.await
|
||||
.map_err(|e| err_with_location(e, maybe_location.as_ref()))?;
|
||||
|
||||
Ok((spec_.clone(), source_file))
|
||||
}
|
||||
.boxed_local();
|
||||
|
||||
self.pending_downloads.push(load_future);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn visit_module(
|
||||
&mut self,
|
||||
module_specifier: &ModuleSpecifier,
|
||||
source_file: SourceFile,
|
||||
) -> Result<(), AnyError> {
|
||||
let mut imports = vec![];
|
||||
let mut referenced_files = vec![];
|
||||
let mut lib_directives = vec![];
|
||||
let mut types_directives = vec![];
|
||||
let mut type_headers = vec![];
|
||||
|
||||
// IMPORTANT: source_file.url might be different than requested
|
||||
// module_specifier because of HTTP redirects. In such
|
||||
// situation we add an "empty" ModuleGraphFile with 'redirect'
|
||||
// field set that will be later used in TS worker when building
|
||||
// map of available source file. It will perform substitution
|
||||
// for proper URL point to redirect target.
|
||||
if module_specifier.as_url() != &source_file.url {
|
||||
// TODO(bartlomieju): refactor, this is a band-aid
|
||||
self.graph.insert(
|
||||
module_specifier.to_string(),
|
||||
ModuleGraphFile {
|
||||
specifier: module_specifier.to_string(),
|
||||
url: module_specifier.to_string(),
|
||||
redirect: Some(source_file.url.to_string()),
|
||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
||||
version_hash: checksum::gen(&[
|
||||
&source_file.source_code.as_bytes(),
|
||||
&version::DENO.as_bytes(),
|
||||
]),
|
||||
media_type: source_file.media_type,
|
||||
source_code: "".to_string(),
|
||||
imports: vec![],
|
||||
referenced_files: vec![],
|
||||
lib_directives: vec![],
|
||||
types_directives: vec![],
|
||||
type_headers: vec![],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let module_specifier = ModuleSpecifier::from(source_file.url.clone());
|
||||
let version_hash = checksum::gen(&[
|
||||
&source_file.source_code.as_bytes(),
|
||||
&version::DENO.as_bytes(),
|
||||
]);
|
||||
let source_code = source_file.source_code.clone();
|
||||
|
||||
if SUPPORTED_MEDIA_TYPES.contains(&source_file.media_type) {
|
||||
if let Some(types_specifier) = source_file.types_header {
|
||||
let type_header = ReferenceDescriptor {
|
||||
specifier: types_specifier.to_string(),
|
||||
resolved_specifier: ModuleSpecifier::resolve_import(
|
||||
&types_specifier,
|
||||
&module_specifier.to_string(),
|
||||
)?,
|
||||
kind: TsReferenceKind::Types,
|
||||
// TODO(bartlomieju): location is not needed in here and constructing
|
||||
// location by hand is bad
|
||||
location: Location {
|
||||
filename: module_specifier.to_string(),
|
||||
line: 0,
|
||||
col: 0,
|
||||
},
|
||||
};
|
||||
self.download_module(
|
||||
type_header.resolved_specifier.clone(),
|
||||
Some(module_specifier.clone()),
|
||||
None,
|
||||
)?;
|
||||
type_headers.push(type_header);
|
||||
}
|
||||
|
||||
let (raw_imports, raw_refs) = pre_process_file(
|
||||
&module_specifier.to_string(),
|
||||
source_file.media_type,
|
||||
&source_code,
|
||||
self.analyze_dynamic_imports,
|
||||
)?;
|
||||
let (imports_, references) = resolve_imports_and_references(
|
||||
module_specifier.clone(),
|
||||
self.maybe_import_map.as_ref(),
|
||||
raw_imports,
|
||||
raw_refs,
|
||||
)?;
|
||||
|
||||
for import_descriptor in imports_ {
|
||||
self.download_module(
|
||||
import_descriptor.resolved_specifier.clone(),
|
||||
Some(module_specifier.clone()),
|
||||
Some(import_descriptor.location.clone()),
|
||||
)?;
|
||||
|
||||
if let Some(type_dir_url) =
|
||||
import_descriptor.resolved_type_directive.as_ref()
|
||||
{
|
||||
self.download_module(
|
||||
type_dir_url.clone(),
|
||||
Some(module_specifier.clone()),
|
||||
Some(import_descriptor.location.clone()),
|
||||
)?;
|
||||
}
|
||||
|
||||
imports.push(import_descriptor);
|
||||
}
|
||||
|
||||
for ref_descriptor in references {
|
||||
self.download_module(
|
||||
ref_descriptor.resolved_specifier.clone(),
|
||||
Some(module_specifier.clone()),
|
||||
Some(ref_descriptor.location.clone()),
|
||||
)?;
|
||||
|
||||
match ref_descriptor.kind {
|
||||
TsReferenceKind::Lib => {
|
||||
lib_directives.push(ref_descriptor);
|
||||
}
|
||||
TsReferenceKind::Types => {
|
||||
types_directives.push(ref_descriptor);
|
||||
}
|
||||
TsReferenceKind::Path => {
|
||||
referenced_files.push(ref_descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.graph.insert(
|
||||
module_specifier.to_string(),
|
||||
ModuleGraphFile {
|
||||
specifier: module_specifier.to_string(),
|
||||
url: module_specifier.to_string(),
|
||||
redirect: None,
|
||||
version_hash,
|
||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
||||
media_type: source_file.media_type,
|
||||
source_code,
|
||||
imports,
|
||||
referenced_files,
|
||||
lib_directives,
|
||||
types_directives,
|
||||
type_headers,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::program_state::ProgramState;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
|
||||
async fn build_graph(
|
||||
module_specifier: &ModuleSpecifier,
|
||||
) -> Result<ModuleGraph, AnyError> {
|
||||
let program_state = ProgramState::new(Default::default()).unwrap();
|
||||
let mut graph_loader = ModuleGraphLoader::new(
|
||||
program_state.file_fetcher.clone(),
|
||||
None,
|
||||
Permissions::allow_all(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
graph_loader.add_to_graph(&module_specifier, None).await?;
|
||||
Ok(graph_loader.get_graph())
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): this test is flaky, because it's using 019_media_types
|
||||
// file, reenable once Python server is replaced with Rust one.
|
||||
#[ignore]
|
||||
#[tokio::test]
|
||||
async fn source_graph_fetch() {
|
||||
let _http_server_guard = test_util::http_server();
|
||||
|
||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
||||
"http://localhost:4545/cli/tests/019_media_types.ts",
|
||||
)
|
||||
.unwrap();
|
||||
let graph = build_graph(&module_specifier)
|
||||
.await
|
||||
.expect("Failed to build graph");
|
||||
|
||||
let a = graph
|
||||
.get("http://localhost:4545/cli/tests/019_media_types.ts")
|
||||
.unwrap();
|
||||
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js"
|
||||
));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts"
|
||||
));
|
||||
assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts"));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts"
|
||||
));
|
||||
assert!(graph.contains_key("http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js"));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js"
|
||||
));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js"
|
||||
));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts"
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&a.imports).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
{
|
||||
"specifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_graph_type_references() {
|
||||
let _http_server_guard = test_util::http_server();
|
||||
|
||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
||||
"http://localhost:4545/cli/tests/type_definitions.ts",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let graph = build_graph(&module_specifier)
|
||||
.await
|
||||
.expect("Failed to build graph");
|
||||
|
||||
eprintln!("json {:#?}", serde_json::to_value(&graph).unwrap());
|
||||
|
||||
let a = graph
|
||||
.get("http://localhost:4545/cli/tests/type_definitions.ts")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&a.imports).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "./type_definitions/foo.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/foo.js",
|
||||
"typeDirective": "./type_definitions/foo.d.ts",
|
||||
"resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/foo.d.ts"
|
||||
},
|
||||
{
|
||||
"specifier": "./type_definitions/fizz.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/fizz.js",
|
||||
"typeDirective": "./type_definitions/fizz.d.ts",
|
||||
"resolvedTypeDirective": "http://localhost:4545/cli/tests/type_definitions/fizz.d.ts"
|
||||
},
|
||||
{
|
||||
"specifier": "./type_definitions/qat.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/type_definitions/qat.ts",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
},
|
||||
])
|
||||
);
|
||||
assert!(graph
|
||||
.contains_key("http://localhost:4545/cli/tests/type_definitions/foo.js"));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/type_definitions/foo.d.ts"
|
||||
));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/type_definitions/fizz.js"
|
||||
));
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/type_definitions/fizz.d.ts"
|
||||
));
|
||||
assert!(graph
|
||||
.contains_key("http://localhost:4545/cli/tests/type_definitions/qat.ts"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_graph_type_references2() {
|
||||
let _http_server_guard = test_util::http_server();
|
||||
|
||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
||||
"http://localhost:4545/cli/tests/type_directives_02.ts",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let graph = build_graph(&module_specifier)
|
||||
.await
|
||||
.expect("Failed to build graph");
|
||||
|
||||
eprintln!("{:#?}", serde_json::to_value(&graph).unwrap());
|
||||
|
||||
let a = graph
|
||||
.get("http://localhost:4545/cli/tests/type_directives_02.ts")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&a.imports).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "./subdir/type_reference.js",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
assert!(graph.contains_key(
|
||||
"http://localhost:4545/cli/tests/subdir/type_reference.d.ts"
|
||||
));
|
||||
|
||||
let b = graph
|
||||
.get("http://localhost:4545/cli/tests/subdir/type_reference.js")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&b.types_directives).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "./type_reference.d.ts",
|
||||
"resolvedSpecifier": "http://localhost:4545/cli/tests/subdir/type_reference.d.ts",
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_graph_type_references3() {
|
||||
let _http_server_guard = test_util::http_server();
|
||||
|
||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
||||
"http://localhost:4545/cli/tests/type_directives_01.ts",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let graph = build_graph(&module_specifier)
|
||||
.await
|
||||
.expect("Failed to build graph");
|
||||
|
||||
let ts = graph
|
||||
.get("http://localhost:4545/cli/tests/type_directives_01.ts")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&ts.imports).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "http://127.0.0.1:4545/xTypeScriptTypes.js",
|
||||
"resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.js",
|
||||
"typeDirective": null,
|
||||
"resolvedTypeDirective": null,
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
let headers = graph
|
||||
.get("http://127.0.0.1:4545/xTypeScriptTypes.js")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&headers.type_headers).unwrap(),
|
||||
json!([
|
||||
{
|
||||
"specifier": "./xTypeScriptTypes.d.ts",
|
||||
"resolvedSpecifier": "http://127.0.0.1:4545/xTypeScriptTypes.d.ts"
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source_graph_different_langs() {
|
||||
let _http_server_guard = test_util::http_server();
|
||||
|
||||
// ModuleGraphLoader was mistakenly parsing this file as TSX
|
||||
// https://github.com/denoland/deno/issues/5867
|
||||
|
||||
let module_specifier = ModuleSpecifier::resolve_url_or_path(
|
||||
"http://localhost:4545/cli/tests/ts_with_generic.ts",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
build_graph(&module_specifier)
|
||||
.await
|
||||
.expect("Failed to build graph");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): use baseline tests from TSC to ensure
|
||||
// compatibility
|
||||
#[test]
|
||||
fn test_pre_process_file() {
|
||||
let source = r#"
|
||||
// This comment is placed to make sure that directives are parsed
|
||||
// even when they start on non-first line
|
||||
|
||||
/// <reference lib="dom" />
|
||||
/// <reference types="./type_reference.d.ts" />
|
||||
/// <reference path="./type_reference/dep.ts" />
|
||||
// @deno-types="./type_definitions/foo.d.ts"
|
||||
import { foo } from "./type_definitions/foo.js";
|
||||
// @deno-types="./type_definitions/fizz.d.ts"
|
||||
import "./type_definitions/fizz.js";
|
||||
|
||||
/// <reference path="./type_reference/dep2.ts" />
|
||||
|
||||
import * as qat from "./type_definitions/qat.ts";
|
||||
|
||||
console.log(foo);
|
||||
console.log(fizz);
|
||||
console.log(qat.qat);
|
||||
"#;
|
||||
|
||||
let (imports, references) =
|
||||
pre_process_file("some/file.ts", MediaType::TypeScript, source, true)
|
||||
.expect("Failed to parse");
|
||||
|
||||
assert_eq!(
|
||||
imports,
|
||||
vec![
|
||||
ImportDesc {
|
||||
specifier: "./type_definitions/foo.js".to_string(),
|
||||
deno_types: Some("./type_definitions/foo.d.ts".to_string()),
|
||||
location: Location {
|
||||
filename: "some/file.ts".to_string(),
|
||||
line: 9,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
ImportDesc {
|
||||
specifier: "./type_definitions/fizz.js".to_string(),
|
||||
deno_types: Some("./type_definitions/fizz.d.ts".to_string()),
|
||||
location: Location {
|
||||
filename: "some/file.ts".to_string(),
|
||||
line: 11,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
ImportDesc {
|
||||
specifier: "./type_definitions/qat.ts".to_string(),
|
||||
deno_types: None,
|
||||
location: Location {
|
||||
filename: "some/file.ts".to_string(),
|
||||
line: 15,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// According to TS docs (https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html)
|
||||
// directives that are not at the top of the file are ignored, so only
|
||||
// 3 references should be captured instead of 4.
|
||||
let file_specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("some/file.ts").unwrap();
|
||||
assert_eq!(
|
||||
references,
|
||||
vec![
|
||||
TsReferenceDesc {
|
||||
specifier: "dom".to_string(),
|
||||
kind: TsReferenceKind::Lib,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 5,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
TsReferenceDesc {
|
||||
specifier: "./type_reference.d.ts".to_string(),
|
||||
kind: TsReferenceKind::Types,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 6,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
TsReferenceDesc {
|
||||
specifier: "./type_reference/dep.ts".to_string(),
|
||||
kind: TsReferenceKind::Path,
|
||||
location: Location {
|
||||
filename: file_specifier.to_string(),
|
||||
line: 7,
|
||||
col: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::ast;
|
||||
use crate::ast::parse;
|
||||
use crate::ast::transpile_module;
|
||||
use crate::ast::BundleHook;
|
||||
use crate::ast::EmitOptions;
|
||||
use crate::ast::Location;
|
||||
use crate::ast::ParsedModule;
|
||||
use crate::colors;
|
||||
|
@ -22,8 +22,7 @@ use crate::specifier_handler::DependencyMap;
|
|||
use crate::specifier_handler::Emit;
|
||||
use crate::specifier_handler::FetchFuture;
|
||||
use crate::specifier_handler::SpecifierHandler;
|
||||
use crate::tsc2::exec;
|
||||
use crate::tsc2::Request;
|
||||
use crate::tsc2;
|
||||
use crate::tsc_config::IgnoredCompilerOptions;
|
||||
use crate::tsc_config::TsConfig;
|
||||
use crate::version;
|
||||
|
@ -35,6 +34,7 @@ use deno_core::futures::stream::StreamExt;
|
|||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde::Serializer;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::ModuleResolutionError;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use regex::Regex;
|
||||
|
@ -121,13 +121,13 @@ impl Error for GraphError {}
|
|||
struct BundleLoader<'a> {
|
||||
cm: Rc<swc_common::SourceMap>,
|
||||
graph: &'a Graph2,
|
||||
emit_options: &'a EmitOptions,
|
||||
emit_options: &'a ast::EmitOptions,
|
||||
}
|
||||
|
||||
impl<'a> BundleLoader<'a> {
|
||||
pub fn new(
|
||||
graph: &'a Graph2,
|
||||
emit_options: &'a EmitOptions,
|
||||
emit_options: &'a ast::EmitOptions,
|
||||
cm: Rc<swc_common::SourceMap>,
|
||||
) -> Self {
|
||||
BundleLoader {
|
||||
|
@ -480,7 +480,7 @@ impl Module {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Stats(pub Vec<(String, u128)>);
|
||||
|
||||
impl<'de> Deserialize<'de> for Stats {
|
||||
|
@ -504,6 +504,23 @@ impl fmt::Display for Stats {
|
|||
}
|
||||
}
|
||||
|
||||
/// A structure that provides information about a module graph result.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ResultInfo {
|
||||
/// A structure which provides diagnostic information (usually from `tsc`)
|
||||
/// about the code in the module graph.
|
||||
pub diagnostics: Diagnostics,
|
||||
/// Optionally ignored compiler options that represent any options that were
|
||||
/// ignored if there was a user provided configuration.
|
||||
pub maybe_ignored_options: Option<IgnoredCompilerOptions>,
|
||||
/// A structure providing key metrics around the operation performed, in
|
||||
/// milliseconds.
|
||||
pub stats: Stats,
|
||||
}
|
||||
|
||||
/// Represents the "default" type library that should be used when type
|
||||
/// checking the code in the module graph. Note that a user provided config
|
||||
/// of `"lib"` would override this value.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum TypeLib {
|
||||
DenoWindow,
|
||||
|
@ -539,7 +556,11 @@ impl Serialize for TypeLib {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BundleOptions {
|
||||
/// If `true` then debug logging will be output from the isolate.
|
||||
pub debug: bool,
|
||||
/// An optional string that points to a user supplied TypeScript configuration
|
||||
/// file that augments the the default configuration passed to the TypeScript
|
||||
/// compiler.
|
||||
pub maybe_config_path: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -560,6 +581,35 @@ pub struct CheckOptions {
|
|||
pub reload: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum BundleType {
|
||||
/// Return the emitted contents of the program as a single "flattened" ES
|
||||
/// module.
|
||||
Esm,
|
||||
// TODO(@kitsonk) once available in swc
|
||||
// Iife,
|
||||
/// Do not bundle the emit, instead returning each of the modules that are
|
||||
/// part of the program as individual files.
|
||||
None,
|
||||
}
|
||||
|
||||
impl Default for BundleType {
|
||||
fn default() -> Self {
|
||||
BundleType::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EmitOptions {
|
||||
/// Indicate the form the result of the emit should take.
|
||||
pub bundle_type: BundleType,
|
||||
/// If `true` then debug logging will be output from the isolate.
|
||||
pub debug: bool,
|
||||
/// An optional map that contains user supplied TypeScript compiler
|
||||
/// configuration options that are passed to the TypeScript compiler.
|
||||
pub maybe_user_config: Option<HashMap<String, Value>>,
|
||||
}
|
||||
|
||||
/// A structure which provides options when transpiling modules.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TranspileOptions {
|
||||
|
@ -647,47 +697,8 @@ impl Graph2 {
|
|||
}));
|
||||
let maybe_ignored_options =
|
||||
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
||||
let emit_options: EmitOptions = ts_config.into();
|
||||
let cm = Rc::new(swc_common::SourceMap::new(
|
||||
swc_common::FilePathMapping::empty(),
|
||||
));
|
||||
let loader = BundleLoader::new(self, &emit_options, cm.clone());
|
||||
let hook = Box::new(BundleHook);
|
||||
let globals = swc_common::Globals::new();
|
||||
let bundler = swc_bundler::Bundler::new(
|
||||
&globals,
|
||||
cm.clone(),
|
||||
loader,
|
||||
self,
|
||||
swc_bundler::Config::default(),
|
||||
hook,
|
||||
);
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert(
|
||||
"bundle".to_string(),
|
||||
swc_common::FileName::Custom(root_specifier.to_string()),
|
||||
);
|
||||
let output = bundler
|
||||
.bundle(entries)
|
||||
.context("Unable to output bundle during Graph2::bundle().")?;
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut emitter = swc_ecmascript::codegen::Emitter {
|
||||
cfg: swc_ecmascript::codegen::Config { minify: false },
|
||||
cm: cm.clone(),
|
||||
comments: None,
|
||||
wr: Box::new(swc_ecmascript::codegen::text_writer::JsWriter::new(
|
||||
cm, "\n", &mut buf, None,
|
||||
)),
|
||||
};
|
||||
|
||||
emitter
|
||||
.emit_module(&output[0].module)
|
||||
.context("Unable to emit bundle during Graph2::bundle().")?;
|
||||
}
|
||||
|
||||
let s = String::from_utf8(buf)
|
||||
.context("Emitted bundle is an invalid utf-8 string.")?;
|
||||
let s = self.emit_bundle(&root_specifier, &ts_config.into())?;
|
||||
let stats = Stats(vec![
|
||||
("Files".to_string(), self.modules.len() as u128),
|
||||
("Total time".to_string(), start.elapsed().as_millis()),
|
||||
|
@ -697,11 +708,7 @@ impl Graph2 {
|
|||
}
|
||||
|
||||
/// Type check the module graph, corresponding to the options provided.
|
||||
pub fn check(
|
||||
self,
|
||||
options: CheckOptions,
|
||||
) -> Result<(Stats, Diagnostics, Option<IgnoredCompilerOptions>), AnyError>
|
||||
{
|
||||
pub fn check(self, options: CheckOptions) -> Result<ResultInfo, AnyError> {
|
||||
let mut config = TsConfig::new(json!({
|
||||
"allowJs": true,
|
||||
// TODO(@kitsonk) is this really needed?
|
||||
|
@ -745,11 +752,10 @@ impl Graph2 {
|
|||
&& (!options.reload || self.roots_dynamic))
|
||||
{
|
||||
debug!("graph does not need to be checked or emitted.");
|
||||
return Ok((
|
||||
Stats(Vec::new()),
|
||||
Diagnostics::default(),
|
||||
return Ok(ResultInfo {
|
||||
maybe_ignored_options,
|
||||
));
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(@kitsonk) not totally happy with this here, but this is the first
|
||||
|
@ -760,26 +766,15 @@ impl Graph2 {
|
|||
info!("{} {}", colors::green("Check"), specifier);
|
||||
}
|
||||
|
||||
let root_names: Vec<(ModuleSpecifier, MediaType)> = self
|
||||
.roots
|
||||
.iter()
|
||||
.map(|ms| {
|
||||
(
|
||||
// root modules can be redirects, so before we pass it to tsc we need
|
||||
// to resolve the redirect
|
||||
self.resolve_specifier(ms).clone(),
|
||||
self.get_media_type(ms).unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let root_names = self.get_root_names();
|
||||
let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone();
|
||||
let hash_data =
|
||||
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
|
||||
let graph = Rc::new(RefCell::new(self));
|
||||
|
||||
let response = exec(
|
||||
let response = tsc2::exec(
|
||||
js::compiler_isolate_init(),
|
||||
Request {
|
||||
tsc2::Request {
|
||||
config: config.clone(),
|
||||
debug: options.debug,
|
||||
graph: graph.clone(),
|
||||
|
@ -837,7 +832,11 @@ impl Graph2 {
|
|||
}
|
||||
graph.flush()?;
|
||||
|
||||
Ok((response.stats, response.diagnostics, maybe_ignored_options))
|
||||
Ok(ResultInfo {
|
||||
diagnostics: response.diagnostics,
|
||||
maybe_ignored_options,
|
||||
stats: response.stats,
|
||||
})
|
||||
}
|
||||
|
||||
fn contains_module(&self, specifier: &ModuleSpecifier) -> bool {
|
||||
|
@ -845,6 +844,165 @@ impl Graph2 {
|
|||
self.modules.contains_key(s)
|
||||
}
|
||||
|
||||
/// Emit the module graph in a specific format. This is specifically designed
|
||||
/// to be an "all-in-one" API for access by the runtime, allowing both
|
||||
/// emitting single modules as well as bundles, using Deno module resolution
|
||||
/// or supplied sources.
|
||||
pub fn emit(
|
||||
self,
|
||||
options: EmitOptions,
|
||||
) -> Result<(HashMap<String, String>, ResultInfo), AnyError> {
|
||||
let mut config = TsConfig::new(json!({
|
||||
"allowJs": true,
|
||||
// TODO(@kitsonk) consider enabling this by default
|
||||
// see: https://github.com/denoland/deno/issues/7732
|
||||
"emitDecoratorMetadata": false,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react",
|
||||
"lib": TypeLib::DenoWindow,
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
}));
|
||||
let opts = match options.bundle_type {
|
||||
BundleType::Esm => json!({
|
||||
"checkJs": false,
|
||||
"inlineSourceMap": false,
|
||||
"noEmit": true,
|
||||
"jsxFactory": "React.createElement",
|
||||
"jsxFragmentFactory": "React.Fragment",
|
||||
}),
|
||||
BundleType::None => json!({
|
||||
"outDir": "deno://",
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
}),
|
||||
};
|
||||
config.merge(&opts);
|
||||
let maybe_ignored_options =
|
||||
if let Some(user_options) = &options.maybe_user_config {
|
||||
config.merge_user_config(user_options)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let root_names = self.get_root_names();
|
||||
let hash_data =
|
||||
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
|
||||
let graph = Rc::new(RefCell::new(self));
|
||||
|
||||
let response = tsc2::exec(
|
||||
js::compiler_isolate_init(),
|
||||
tsc2::Request {
|
||||
config: config.clone(),
|
||||
debug: options.debug,
|
||||
graph: graph.clone(),
|
||||
hash_data,
|
||||
maybe_tsbuildinfo: None,
|
||||
root_names,
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut emitted_files = HashMap::new();
|
||||
match options.bundle_type {
|
||||
BundleType::Esm => {
|
||||
assert!(
|
||||
response.emitted_files.is_empty(),
|
||||
"No files should have been emitted from tsc."
|
||||
);
|
||||
let graph = graph.borrow();
|
||||
assert_eq!(
|
||||
graph.roots.len(),
|
||||
1,
|
||||
"Only a single root module supported."
|
||||
);
|
||||
let specifier = &graph.roots[0];
|
||||
let s = graph.emit_bundle(specifier, &config.into())?;
|
||||
emitted_files.insert("deno:///bundle.js".to_string(), s);
|
||||
}
|
||||
BundleType::None => {
|
||||
for emitted_file in &response.emitted_files {
|
||||
assert!(
|
||||
emitted_file.maybe_specifiers.is_some(),
|
||||
"Orphaned file emitted."
|
||||
);
|
||||
let specifiers = emitted_file.maybe_specifiers.clone().unwrap();
|
||||
assert_eq!(
|
||||
specifiers.len(),
|
||||
1,
|
||||
"An unexpected number of specifiers associated with emitted file."
|
||||
);
|
||||
let specifier = specifiers[0].clone();
|
||||
let extension = match emitted_file.media_type {
|
||||
MediaType::JavaScript => ".js",
|
||||
MediaType::SourceMap => ".js.map",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let key = format!("{}{}", specifier, extension);
|
||||
emitted_files.insert(key, emitted_file.data.clone());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
emitted_files,
|
||||
ResultInfo {
|
||||
diagnostics: response.diagnostics,
|
||||
maybe_ignored_options,
|
||||
stats: response.stats,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Shared between `bundle()` and `emit()`.
|
||||
fn emit_bundle(
|
||||
&self,
|
||||
specifier: &ModuleSpecifier,
|
||||
emit_options: &ast::EmitOptions,
|
||||
) -> Result<String, AnyError> {
|
||||
let cm = Rc::new(swc_common::SourceMap::new(
|
||||
swc_common::FilePathMapping::empty(),
|
||||
));
|
||||
let loader = BundleLoader::new(self, emit_options, cm.clone());
|
||||
let hook = Box::new(BundleHook);
|
||||
let globals = swc_common::Globals::new();
|
||||
let bundler = swc_bundler::Bundler::new(
|
||||
&globals,
|
||||
cm.clone(),
|
||||
loader,
|
||||
self,
|
||||
swc_bundler::Config::default(),
|
||||
hook,
|
||||
);
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert(
|
||||
"bundle".to_string(),
|
||||
swc_common::FileName::Custom(specifier.to_string()),
|
||||
);
|
||||
let output = bundler
|
||||
.bundle(entries)
|
||||
.context("Unable to output bundle during Graph2::bundle().")?;
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut emitter = swc_ecmascript::codegen::Emitter {
|
||||
cfg: swc_ecmascript::codegen::Config { minify: false },
|
||||
cm: cm.clone(),
|
||||
comments: None,
|
||||
wr: Box::new(swc_ecmascript::codegen::text_writer::JsWriter::new(
|
||||
cm, "\n", &mut buf, None,
|
||||
)),
|
||||
};
|
||||
|
||||
emitter
|
||||
.emit_module(&output[0].module)
|
||||
.context("Unable to emit bundle during Graph2::bundle().")?;
|
||||
}
|
||||
|
||||
String::from_utf8(buf).context("Emitted bundle is an invalid utf-8 string.")
|
||||
}
|
||||
|
||||
/// Update the handler with any modules that are marked as _dirty_ and update
|
||||
/// any build info if present.
|
||||
fn flush(&mut self) -> Result<(), AnyError> {
|
||||
|
@ -963,22 +1121,6 @@ impl Graph2 {
|
|||
self.modules.get(s)
|
||||
}
|
||||
|
||||
/// Consume graph and return list of all module specifiers
|
||||
/// contained in the graph.
|
||||
pub fn get_modules(&self) -> Vec<ModuleSpecifier> {
|
||||
self.modules.keys().map(|s| s.to_owned()).collect()
|
||||
}
|
||||
|
||||
/// Get the source for a given module specifier. If the module is not part
|
||||
/// of the graph, the result will be `None`.
|
||||
pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
if let Some(module) = self.get_module(specifier) {
|
||||
Some(module.source.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_module_mut(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
|
@ -993,6 +1135,41 @@ impl Graph2 {
|
|||
self.modules.get_mut(s)
|
||||
}
|
||||
|
||||
/// Consume graph and return list of all module specifiers contained in the
|
||||
/// graph.
|
||||
pub fn get_modules(&self) -> Vec<ModuleSpecifier> {
|
||||
self.modules.keys().map(|s| s.to_owned()).collect()
|
||||
}
|
||||
|
||||
/// Transform `self.roots` into something that works for `tsc`, because `tsc`
|
||||
/// doesn't like root names without extensions that match its expectations,
|
||||
/// nor does it have any concept of redirection, so we have to resolve all
|
||||
/// that upfront before feeding it to `tsc`.
|
||||
fn get_root_names(&self) -> Vec<(ModuleSpecifier, MediaType)> {
|
||||
self
|
||||
.roots
|
||||
.iter()
|
||||
.map(|ms| {
|
||||
(
|
||||
// root modules can be redirects, so before we pass it to tsc we need
|
||||
// to resolve the redirect
|
||||
self.resolve_specifier(ms).clone(),
|
||||
self.get_media_type(ms).unwrap(),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the source for a given module specifier. If the module is not part
|
||||
/// of the graph, the result will be `None`.
|
||||
pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> {
|
||||
if let Some(module) = self.get_module(specifier) {
|
||||
Some(module.source.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a structure which provides information about the module graph and
|
||||
/// the relationship of the modules in the graph. This structure is used to
|
||||
/// provide information for the `info` subcommand.
|
||||
|
@ -1209,7 +1386,7 @@ impl Graph2 {
|
|||
let maybe_ignored_options =
|
||||
ts_config.merge_tsconfig(options.maybe_config_path)?;
|
||||
|
||||
let emit_options: EmitOptions = ts_config.clone().into();
|
||||
let emit_options: ast::EmitOptions = ts_config.clone().into();
|
||||
|
||||
let mut emit_count: u128 = 0;
|
||||
let config = ts_config.as_bytes();
|
||||
|
@ -1434,12 +1611,25 @@ impl GraphBuilder2 {
|
|||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::specifier_handler::MemoryHandler;
|
||||
use deno_core::futures::future;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
macro_rules! map (
|
||||
{ $($key:expr => $value:expr),+ } => {
|
||||
{
|
||||
let mut m = ::std::collections::HashMap::new();
|
||||
$(
|
||||
m.insert($key, $value);
|
||||
)+
|
||||
m
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
/// This is a testing mock for `SpecifierHandler` that uses a special file
|
||||
/// system renaming to mock local and remote modules as well as provides
|
||||
/// "spies" for the critical methods for testing purposes.
|
||||
|
@ -1465,20 +1655,7 @@ pub mod tests {
|
|||
.replace("://", "_")
|
||||
.replace("/", "-");
|
||||
let source_path = self.fixtures.join(specifier_text);
|
||||
let media_type = match source_path.extension().unwrap().to_str().unwrap()
|
||||
{
|
||||
"ts" => {
|
||||
if source_path.to_string_lossy().ends_with(".d.ts") {
|
||||
MediaType::Dts
|
||||
} else {
|
||||
MediaType::TypeScript
|
||||
}
|
||||
}
|
||||
"tsx" => MediaType::TSX,
|
||||
"js" => MediaType::JavaScript,
|
||||
"jsx" => MediaType::JSX,
|
||||
_ => MediaType::Unknown,
|
||||
};
|
||||
let media_type = MediaType::from(&source_path);
|
||||
let source = fs::read_to_string(&source_path)?;
|
||||
let is_remote = specifier.as_url().scheme() != "file";
|
||||
|
||||
|
@ -1572,6 +1749,24 @@ pub mod tests {
|
|||
(builder.get_graph(), handler)
|
||||
}
|
||||
|
||||
async fn setup_memory(
|
||||
specifier: ModuleSpecifier,
|
||||
sources: HashMap<&str, &str>,
|
||||
) -> Graph2 {
|
||||
let sources: HashMap<String, String> = sources
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect();
|
||||
let handler = Rc::new(RefCell::new(MemoryHandler::new(sources)));
|
||||
let mut builder = GraphBuilder2::new(handler.clone(), None, None);
|
||||
builder
|
||||
.add(&specifier, false)
|
||||
.await
|
||||
.expect("module not inserted");
|
||||
|
||||
builder.get_graph()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_version() {
|
||||
let doc_a = "console.log(42);";
|
||||
|
@ -1694,7 +1889,7 @@ pub mod tests {
|
|||
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
||||
.expect("could not resolve module");
|
||||
let (graph, handler) = setup(specifier).await;
|
||||
let (stats, diagnostics, maybe_ignored_options) = graph
|
||||
let result_info = graph
|
||||
.check(CheckOptions {
|
||||
debug: false,
|
||||
emit: true,
|
||||
|
@ -1703,9 +1898,9 @@ pub mod tests {
|
|||
reload: false,
|
||||
})
|
||||
.expect("should have checked");
|
||||
assert!(maybe_ignored_options.is_none());
|
||||
assert_eq!(stats.0.len(), 12);
|
||||
assert!(diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert_eq!(result_info.stats.0.len(), 12);
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
let h = handler.borrow();
|
||||
assert_eq!(h.cache_calls.len(), 2);
|
||||
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
||||
|
@ -1717,7 +1912,7 @@ pub mod tests {
|
|||
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
|
||||
.expect("could not resolve module");
|
||||
let (graph, handler) = setup(specifier).await;
|
||||
let (stats, diagnostics, maybe_ignored_options) = graph
|
||||
let result_info = graph
|
||||
.check(CheckOptions {
|
||||
debug: false,
|
||||
emit: false,
|
||||
|
@ -1726,9 +1921,9 @@ pub mod tests {
|
|||
reload: false,
|
||||
})
|
||||
.expect("should have checked");
|
||||
assert!(maybe_ignored_options.is_none());
|
||||
assert_eq!(stats.0.len(), 12);
|
||||
assert!(diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert_eq!(result_info.stats.0.len(), 12);
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
let h = handler.borrow();
|
||||
assert_eq!(h.cache_calls.len(), 0);
|
||||
assert_eq!(h.tsbuildinfo_calls.len(), 1);
|
||||
|
@ -1740,7 +1935,7 @@ pub mod tests {
|
|||
ModuleSpecifier::resolve_url_or_path("file:///tests/checkwithconfig.ts")
|
||||
.expect("could not resolve module");
|
||||
let (graph, handler) = setup(specifier.clone()).await;
|
||||
let (_, diagnostics, maybe_ignored_options) = graph
|
||||
let result_info = graph
|
||||
.check(CheckOptions {
|
||||
debug: false,
|
||||
emit: true,
|
||||
|
@ -1751,8 +1946,8 @@ pub mod tests {
|
|||
reload: true,
|
||||
})
|
||||
.expect("should have checked");
|
||||
assert!(maybe_ignored_options.is_none());
|
||||
assert!(diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
let h = handler.borrow();
|
||||
assert_eq!(h.version_calls.len(), 2);
|
||||
let ver0 = h.version_calls[0].1.clone();
|
||||
|
@ -1760,7 +1955,7 @@ pub mod tests {
|
|||
|
||||
// let's do it all over again to ensure that the versions are determinstic
|
||||
let (graph, handler) = setup(specifier).await;
|
||||
let (_, diagnostics, maybe_ignored_options) = graph
|
||||
let result_info = graph
|
||||
.check(CheckOptions {
|
||||
debug: false,
|
||||
emit: true,
|
||||
|
@ -1771,14 +1966,89 @@ pub mod tests {
|
|||
reload: true,
|
||||
})
|
||||
.expect("should have checked");
|
||||
assert!(maybe_ignored_options.is_none());
|
||||
assert!(diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
let h = handler.borrow();
|
||||
assert_eq!(h.version_calls.len(), 2);
|
||||
assert!(h.version_calls[0].1 == ver0 || h.version_calls[0].1 == ver1);
|
||||
assert!(h.version_calls[1].1 == ver0 || h.version_calls[1].1 == ver1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_graph_emit() {
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||
let graph = setup_memory(
|
||||
specifier,
|
||||
map!(
|
||||
"/a.ts" => r#"
|
||||
import * as b from "./b.ts";
|
||||
|
||||
console.log(b);
|
||||
"#,
|
||||
"/b.ts" => r#"
|
||||
export const b = "b";
|
||||
"#
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let (emitted_files, result_info) = graph
|
||||
.emit(EmitOptions {
|
||||
bundle_type: BundleType::None,
|
||||
debug: false,
|
||||
maybe_user_config: None,
|
||||
})
|
||||
.expect("should have emitted");
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert_eq!(emitted_files.len(), 4);
|
||||
let out_a = emitted_files.get("file:///a.ts.js");
|
||||
assert!(out_a.is_some());
|
||||
let out_a = out_a.unwrap();
|
||||
assert!(out_a.starts_with("import * as b from"));
|
||||
assert!(emitted_files.contains_key("file:///a.ts.js.map"));
|
||||
let out_b = emitted_files.get("file:///b.ts.js");
|
||||
assert!(out_b.is_some());
|
||||
let out_b = out_b.unwrap();
|
||||
assert!(out_b.starts_with("export const b = \"b\";"));
|
||||
assert!(emitted_files.contains_key("file:///b.ts.js.map"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_graph_emit_bundle() {
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||
let graph = setup_memory(
|
||||
specifier,
|
||||
map!(
|
||||
"/a.ts" => r#"
|
||||
import * as b from "./b.ts";
|
||||
|
||||
console.log(b);
|
||||
"#,
|
||||
"/b.ts" => r#"
|
||||
export const b = "b";
|
||||
"#
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let (emitted_files, result_info) = graph
|
||||
.emit(EmitOptions {
|
||||
bundle_type: BundleType::Esm,
|
||||
debug: false,
|
||||
maybe_user_config: None,
|
||||
})
|
||||
.expect("should have emitted");
|
||||
assert!(result_info.diagnostics.is_empty());
|
||||
assert!(result_info.maybe_ignored_options.is_none());
|
||||
assert_eq!(emitted_files.len(), 1);
|
||||
let actual = emitted_files.get("deno:///bundle.js");
|
||||
assert!(actual.is_some());
|
||||
let actual = actual.unwrap();
|
||||
assert!(actual.contains("const b = \"b\";"));
|
||||
assert!(actual.contains("console.log(b);"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_graph_info() {
|
||||
let specifier =
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::module_graph2::TypeLib;
|
||||
use crate::permissions::Permissions;
|
||||
use crate::program_state::ProgramState;
|
||||
use crate::tsc::TargetLib;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future::FutureExt;
|
||||
use deno_core::futures::Future;
|
||||
|
@ -21,7 +21,7 @@ pub struct CliModuleLoader {
|
|||
/// When flags contains a `.import_map_path` option, the content of the
|
||||
/// import map file will be resolved and set.
|
||||
pub import_map: Option<ImportMap>,
|
||||
pub target_lib: TargetLib,
|
||||
pub lib: TypeLib,
|
||||
pub is_main: bool,
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ impl CliModuleLoader {
|
|||
pub fn new(maybe_import_map: Option<ImportMap>) -> Rc<Self> {
|
||||
Rc::new(CliModuleLoader {
|
||||
import_map: maybe_import_map,
|
||||
target_lib: TargetLib::Main,
|
||||
lib: TypeLib::DenoWindow,
|
||||
is_main: true,
|
||||
})
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ impl CliModuleLoader {
|
|||
pub fn new_for_worker() -> Rc<Self> {
|
||||
Rc::new(CliModuleLoader {
|
||||
import_map: None,
|
||||
target_lib: TargetLib::Worker,
|
||||
lib: TypeLib::DenoWorker,
|
||||
is_main: false,
|
||||
})
|
||||
}
|
||||
|
@ -117,13 +117,21 @@ impl ModuleLoader for CliModuleLoader {
|
|||
is_dynamic: bool,
|
||||
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
|
||||
let specifier = specifier.clone();
|
||||
let target_lib = self.target_lib.clone();
|
||||
let maybe_import_map = self.import_map.clone();
|
||||
let state = op_state.borrow();
|
||||
|
||||
// The permissions that should be applied to any dynamically imported module
|
||||
let dynamic_permissions = state.borrow::<Permissions>().clone();
|
||||
let program_state = state.borrow::<Arc<ProgramState>>().clone();
|
||||
let lib = if program_state.flags.unstable {
|
||||
if self.lib == TypeLib::DenoWindow {
|
||||
TypeLib::UnstableDenoWindow
|
||||
} else {
|
||||
TypeLib::UnstableDenoWorker
|
||||
}
|
||||
} else {
|
||||
self.lib.clone()
|
||||
};
|
||||
drop(state);
|
||||
|
||||
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param
|
||||
|
@ -131,7 +139,7 @@ impl ModuleLoader for CliModuleLoader {
|
|||
program_state
|
||||
.prepare_module_load(
|
||||
specifier,
|
||||
target_lib,
|
||||
lib,
|
||||
dynamic_permissions,
|
||||
is_dynamic,
|
||||
maybe_import_map,
|
||||
|
|
|
@ -17,8 +17,6 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
|||
};
|
||||
}
|
||||
match name {
|
||||
"system_loader.js" => Some(include_str!("system_loader.js")),
|
||||
"system_loader_es5.js" => Some(include_str!("system_loader_es5.js")),
|
||||
"bootstrap.ts" => Some("console.log(\"hello deno\");"),
|
||||
"typescript.d.ts" => inc!("typescript.d.ts"),
|
||||
"lib.dom.d.ts" => inc!("lib.dom.d.ts"),
|
||||
|
@ -85,6 +83,11 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
|
|||
|
||||
/// Warning: Returns a non-JSON op dispatcher. Must be manually attached to
|
||||
/// JsRuntime.
|
||||
///
|
||||
/// TODO(@kitsonk) this is only used when building the snapshot, and needs to
|
||||
/// be refactored somewhere else. It is no longer used by `main.rs` and
|
||||
/// therefore requires the allow unused.
|
||||
#[allow(unused)]
|
||||
pub fn op_fetch_asset(
|
||||
custom_assets: HashMap<String, PathBuf>,
|
||||
) -> impl Fn(Rc<RefCell<OpState>>, BufVec) -> Op {
|
||||
|
|
|
@ -3,17 +3,23 @@
|
|||
use crate::ast;
|
||||
use crate::colors;
|
||||
use crate::media_type::MediaType;
|
||||
use crate::module_graph2::BundleType;
|
||||
use crate::module_graph2::EmitOptions;
|
||||
use crate::module_graph2::GraphBuilder2;
|
||||
use crate::permissions::Permissions;
|
||||
use crate::tsc::runtime_bundle;
|
||||
use crate::tsc::runtime_compile;
|
||||
use crate::specifier_handler::FetchHandler;
|
||||
use crate::specifier_handler::MemoryHandler;
|
||||
use crate::specifier_handler::SpecifierHandler;
|
||||
use crate::tsc_config;
|
||||
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::error::Context;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::BufVec;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::OpState;
|
||||
use serde::Deserialize;
|
||||
use std::cell::RefCell;
|
||||
|
@ -39,35 +45,53 @@ async fn op_compile(
|
|||
args: Value,
|
||||
_data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
super::check_unstable2(&state, "Deno.compile");
|
||||
let args: CompileArgs = serde_json::from_value(args)?;
|
||||
let cli_state = super::global_state2(&state);
|
||||
let program_state = cli_state.clone();
|
||||
let permissions = {
|
||||
if args.bundle {
|
||||
super::check_unstable2(&state, "Deno.bundle");
|
||||
} else {
|
||||
super::check_unstable2(&state, "Deno.compile");
|
||||
}
|
||||
let program_state = super::global_state2(&state);
|
||||
let runtime_permissions = {
|
||||
let state = state.borrow();
|
||||
state.borrow::<Permissions>().clone()
|
||||
};
|
||||
let fut = if args.bundle {
|
||||
runtime_bundle(
|
||||
&program_state,
|
||||
permissions,
|
||||
&args.root_name,
|
||||
&args.sources,
|
||||
&args.options,
|
||||
)
|
||||
.boxed_local()
|
||||
let handler: Rc<RefCell<dyn SpecifierHandler>> =
|
||||
if let Some(sources) = args.sources {
|
||||
Rc::new(RefCell::new(MemoryHandler::new(sources)))
|
||||
} else {
|
||||
Rc::new(RefCell::new(FetchHandler::new(
|
||||
&program_state,
|
||||
runtime_permissions,
|
||||
)?))
|
||||
};
|
||||
let mut builder = GraphBuilder2::new(handler, None, None);
|
||||
let specifier = ModuleSpecifier::resolve_url_or_path(&args.root_name)
|
||||
.context("The root specifier is invalid.")?;
|
||||
builder.add(&specifier, false).await?;
|
||||
let graph = builder.get_graph();
|
||||
let bundle_type = if args.bundle {
|
||||
BundleType::Esm
|
||||
} else {
|
||||
runtime_compile(
|
||||
&program_state,
|
||||
permissions,
|
||||
&args.root_name,
|
||||
&args.sources,
|
||||
&args.options,
|
||||
)
|
||||
.boxed_local()
|
||||
BundleType::None
|
||||
};
|
||||
let result = fut.await?;
|
||||
Ok(result)
|
||||
let debug = program_state.flags.log_level == Some(log::Level::Debug);
|
||||
let maybe_user_config: Option<HashMap<String, Value>> =
|
||||
if let Some(options) = args.options {
|
||||
Some(serde_json::from_str(&options)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (emitted_files, result_info) = graph.emit(EmitOptions {
|
||||
bundle_type,
|
||||
debug,
|
||||
maybe_user_config,
|
||||
})?;
|
||||
|
||||
Ok(json!({
|
||||
"emittedFiles": emitted_files,
|
||||
"diagnostics": result_info.diagnostics,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
|
|
@ -15,9 +15,6 @@ use crate::module_graph2::TypeLib;
|
|||
use crate::permissions::Permissions;
|
||||
use crate::source_maps::SourceMapGetter;
|
||||
use crate::specifier_handler::FetchHandler;
|
||||
use crate::tsc::CompiledModule;
|
||||
use crate::tsc::TargetLib;
|
||||
use crate::tsc::TsCompiler;
|
||||
|
||||
use deno_core::error::generic_error;
|
||||
use deno_core::error::AnyError;
|
||||
|
@ -37,6 +34,12 @@ pub fn exit_unstable(api_name: &str) {
|
|||
std::process::exit(70);
|
||||
}
|
||||
|
||||
// TODO(@kitsonk) probably can refactor this better with the graph.
|
||||
pub struct CompiledModule {
|
||||
pub code: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// This structure represents state of single "deno" program.
|
||||
///
|
||||
/// It is shared by all created workers (thus V8 isolates).
|
||||
|
@ -47,7 +50,6 @@ pub struct ProgramState {
|
|||
pub permissions: Permissions,
|
||||
pub dir: deno_dir::DenoDir,
|
||||
pub file_fetcher: SourceFileFetcher,
|
||||
pub ts_compiler: TsCompiler,
|
||||
pub lockfile: Option<Arc<Mutex<Lockfile>>>,
|
||||
pub maybe_import_map: Option<ImportMap>,
|
||||
pub maybe_inspector_server: Option<Arc<InspectorServer>>,
|
||||
|
@ -70,12 +72,6 @@ impl ProgramState {
|
|||
ca_file.as_deref(),
|
||||
)?;
|
||||
|
||||
let ts_compiler = TsCompiler::new(
|
||||
file_fetcher.clone(),
|
||||
flags.clone(),
|
||||
dir.gen_cache.clone(),
|
||||
)?;
|
||||
|
||||
let lockfile = if let Some(filename) = &flags.lock {
|
||||
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
|
||||
Some(Arc::new(Mutex::new(lockfile)))
|
||||
|
@ -105,7 +101,6 @@ impl ProgramState {
|
|||
permissions: Permissions::from_flags(&flags),
|
||||
flags,
|
||||
file_fetcher,
|
||||
ts_compiler,
|
||||
lockfile,
|
||||
maybe_import_map,
|
||||
maybe_inspector_server,
|
||||
|
@ -120,7 +115,7 @@ impl ProgramState {
|
|||
pub async fn prepare_module_load(
|
||||
self: &Arc<Self>,
|
||||
specifier: ModuleSpecifier,
|
||||
target_lib: TargetLib,
|
||||
lib: TypeLib,
|
||||
runtime_permissions: Permissions,
|
||||
is_dynamic: bool,
|
||||
maybe_import_map: Option<ImportMap>,
|
||||
|
@ -129,7 +124,7 @@ impl ProgramState {
|
|||
// Workers are subject to the current runtime permissions. We do the
|
||||
// permission check here early to avoid "wasting" time building a module
|
||||
// graph for a module that cannot be loaded.
|
||||
if target_lib == TargetLib::Worker {
|
||||
if lib == TypeLib::DenoWorker || lib == TypeLib::UnstableDenoWorker {
|
||||
runtime_permissions.check_specifier(&specifier)?;
|
||||
}
|
||||
let handler =
|
||||
|
@ -153,37 +148,20 @@ impl ProgramState {
|
|||
eprintln!("{}", ignored_options);
|
||||
}
|
||||
} else {
|
||||
let lib = match target_lib {
|
||||
TargetLib::Main => {
|
||||
if self.flags.unstable {
|
||||
TypeLib::UnstableDenoWindow
|
||||
} else {
|
||||
TypeLib::DenoWindow
|
||||
}
|
||||
}
|
||||
TargetLib::Worker => {
|
||||
if self.flags.unstable {
|
||||
TypeLib::UnstableDenoWorker
|
||||
} else {
|
||||
TypeLib::DenoWorker
|
||||
}
|
||||
}
|
||||
};
|
||||
let (stats, diagnostics, maybe_ignored_options) =
|
||||
graph.check(CheckOptions {
|
||||
debug,
|
||||
emit: true,
|
||||
lib,
|
||||
maybe_config_path,
|
||||
reload: self.flags.reload,
|
||||
})?;
|
||||
let result_info = graph.check(CheckOptions {
|
||||
debug,
|
||||
emit: true,
|
||||
lib,
|
||||
maybe_config_path,
|
||||
reload: self.flags.reload,
|
||||
})?;
|
||||
|
||||
debug!("{}", stats);
|
||||
if let Some(ignored_options) = maybe_ignored_options {
|
||||
debug!("{}", result_info.stats);
|
||||
if let Some(ignored_options) = result_info.maybe_ignored_options {
|
||||
eprintln!("{}", ignored_options);
|
||||
}
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(generic_error(diagnostics.to_string()));
|
||||
if !result_info.diagnostics.is_empty() {
|
||||
return Err(generic_error(result_info.diagnostics.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -52,19 +52,14 @@
|
|||
sources: !!sources,
|
||||
options,
|
||||
});
|
||||
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||
const result = await opCompile(payload);
|
||||
util.assert(result.emitMap);
|
||||
util.assert(result.emittedFiles);
|
||||
const maybeDiagnostics = result.diagnostics.length === 0
|
||||
? undefined
|
||||
: result.diagnostics;
|
||||
|
||||
const emitMap = {};
|
||||
|
||||
for (const [key, emittedSource] of Object.entries(result.emitMap)) {
|
||||
emitMap[key] = emittedSource.contents;
|
||||
}
|
||||
|
||||
return [maybeDiagnostics, emitMap];
|
||||
return [maybeDiagnostics, result.emittedFiles];
|
||||
}
|
||||
|
||||
// TODO(bartlomieju): change return type to interface?
|
||||
|
@ -84,12 +79,14 @@
|
|||
sources: !!sources,
|
||||
options,
|
||||
});
|
||||
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
|
||||
const result = await opCompile(payload);
|
||||
util.assert(result.output);
|
||||
let output = result.emittedFiles["deno:///bundle.js"];
|
||||
util.assert(output);
|
||||
const maybeDiagnostics = result.diagnostics.length === 0
|
||||
? undefined
|
||||
: result.diagnostics;
|
||||
return [maybeDiagnostics, result.output];
|
||||
return [maybeDiagnostics, output];
|
||||
}
|
||||
|
||||
window.__bootstrap.compilerApi = {
|
||||
|
|
|
@ -8,7 +8,9 @@ use crate::media_type::MediaType;
|
|||
use crate::permissions::Permissions;
|
||||
use crate::program_state::ProgramState;
|
||||
|
||||
use deno_core::error::custom_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::future;
|
||||
use deno_core::futures::Future;
|
||||
use deno_core::futures::FutureExt;
|
||||
use deno_core::serde_json;
|
||||
|
@ -61,7 +63,6 @@ pub struct CachedModule {
|
|||
pub specifier: ModuleSpecifier,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Default for CachedModule {
|
||||
fn default() -> Self {
|
||||
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap();
|
||||
|
@ -422,12 +423,119 @@ impl SpecifierHandler for FetchHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct MemoryHandler {
|
||||
sources: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl MemoryHandler {
|
||||
pub fn new(sources: HashMap<String, String>) -> Self {
|
||||
Self { sources }
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecifierHandler for MemoryHandler {
|
||||
fn fetch(
|
||||
&mut self,
|
||||
specifier: ModuleSpecifier,
|
||||
_maybe_referrer: Option<Location>,
|
||||
_is_dynamic: bool,
|
||||
) -> FetchFuture {
|
||||
let mut specifier_text = specifier.to_string();
|
||||
if !self.sources.contains_key(&specifier_text) {
|
||||
specifier_text = specifier_text.replace("file:///", "/");
|
||||
if !self.sources.contains_key(&specifier_text) {
|
||||
// Convert `C:/a/path/file.ts` to `/a/path/file.ts`
|
||||
specifier_text = specifier_text[3..].to_string()
|
||||
}
|
||||
}
|
||||
let result = if let Some(source) = self.sources.get(&specifier_text) {
|
||||
let media_type = MediaType::from(&specifier);
|
||||
let is_remote = specifier.as_url().scheme() != "file";
|
||||
|
||||
Ok(CachedModule {
|
||||
source: source.to_string(),
|
||||
requested_specifier: specifier.clone(),
|
||||
specifier,
|
||||
media_type,
|
||||
is_remote,
|
||||
..Default::default()
|
||||
})
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"NotFound",
|
||||
format!("Unable to find specifier in sources: {}", specifier),
|
||||
))
|
||||
};
|
||||
|
||||
Box::pin(future::ready(result))
|
||||
}
|
||||
|
||||
fn get_tsbuildinfo(
|
||||
&self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
) -> Result<Option<String>, AnyError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn set_cache(
|
||||
&mut self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_emit: &Emit,
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_types(
|
||||
&mut self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_types: String,
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tsbuildinfo(
|
||||
&mut self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_tsbuildinfo: String,
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_deps(
|
||||
&mut self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_dependencies: DependencyMap,
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_version(
|
||||
&mut self,
|
||||
_specifier: &ModuleSpecifier,
|
||||
_version: String,
|
||||
) -> Result<(), AnyError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::http_cache::HttpCache;
|
||||
use tempfile::TempDir;
|
||||
|
||||
macro_rules! map (
|
||||
{ $($key:expr => $value:expr),+ } => {
|
||||
{
|
||||
let mut m = ::std::collections::HashMap::new();
|
||||
$(
|
||||
m.insert($key, $value);
|
||||
)+
|
||||
m
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
fn setup() -> (TempDir, FetchHandler) {
|
||||
let temp_dir = TempDir::new().expect("could not setup");
|
||||
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf()))
|
||||
|
@ -522,4 +630,111 @@ pub mod tests {
|
|||
file_fetcher.fetch(specifier, None, false).await.unwrap();
|
||||
assert_eq!(cached_module.is_remote, false);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_memory_handler_fetch() {
|
||||
let a_src = r#"
|
||||
import * as b from "./b.ts";
|
||||
console.log(b);
|
||||
"#;
|
||||
let b_src = r#"
|
||||
export const b = "b";
|
||||
"#;
|
||||
let c_src = r#"
|
||||
export const c = "c";
|
||||
"#;
|
||||
let d_src = r#"
|
||||
export const d: string;
|
||||
"#;
|
||||
let sources = map!(
|
||||
"/a.ts" => a_src,
|
||||
"/b.ts" => b_src,
|
||||
"https://deno.land/x/c.js" => c_src,
|
||||
"https://deno.land/x/d.d.ts" => d_src
|
||||
);
|
||||
let sources: HashMap<String, String> = sources
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
.collect();
|
||||
let mut handler = MemoryHandler::new(sources);
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("file:///a.ts").unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, a_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||
assert_eq!(actual.is_remote, false);
|
||||
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("file:///b.ts").unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, b_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||
assert_eq!(actual.is_remote, false);
|
||||
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/c.js").unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, c_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::JavaScript);
|
||||
assert_eq!(actual.is_remote, true);
|
||||
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/d.d.ts")
|
||||
.unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, d_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::Dts);
|
||||
assert_eq!(actual.is_remote, true);
|
||||
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("https://deno.land/x/missing.ts")
|
||||
.unwrap();
|
||||
handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect_err("should have errored");
|
||||
|
||||
let specifier = ModuleSpecifier::resolve_url_or_path("/a.ts").unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, a_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||
assert_eq!(actual.is_remote, false);
|
||||
|
||||
let specifier =
|
||||
ModuleSpecifier::resolve_url_or_path("file:///C:/a.ts").unwrap();
|
||||
let actual: CachedModule = handler
|
||||
.fetch(specifier.clone(), None, false)
|
||||
.await
|
||||
.expect("could not fetch module");
|
||||
assert_eq!(actual.source, a_src.to_string());
|
||||
assert_eq!(actual.requested_specifier, specifier);
|
||||
assert_eq!(actual.specifier, specifier);
|
||||
assert_eq!(actual.media_type, MediaType::TypeScript);
|
||||
assert_eq!(actual.is_remote, false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,11 @@ Deno.test({
|
|||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), [
|
||||
"/bar.js.map",
|
||||
"/bar.js",
|
||||
"/foo.js.map",
|
||||
"/foo.js",
|
||||
]);
|
||||
const keys = Object.keys(actual).sort();
|
||||
assert(keys[0].endsWith("/bar.ts.js"));
|
||||
assert(keys[1].endsWith("/bar.ts.js.map"));
|
||||
assert(keys[2].endsWith("/foo.ts.js"));
|
||||
assert(keys[3].endsWith("/foo.ts.js.map"));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -29,10 +28,10 @@ Deno.test({
|
|||
const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts");
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
const keys = Object.keys(actual);
|
||||
const keys = Object.keys(actual).sort();
|
||||
assertEquals(keys.length, 6);
|
||||
assert(keys[0].endsWith("print_hello.js.map"));
|
||||
assert(keys[1].endsWith("print_hello.js"));
|
||||
assert(keys[0].endsWith("cli/tests/subdir/mod1.ts.js"));
|
||||
assert(keys[1].endsWith("cli/tests/subdir/mod1.ts.js.map"));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -51,8 +50,11 @@ Deno.test({
|
|||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["/foo.js"]);
|
||||
assert(actual["/foo.js"].startsWith("define("));
|
||||
const keys = Object.keys(actual);
|
||||
assertEquals(keys.length, 1);
|
||||
const key = keys[0];
|
||||
assert(key.endsWith("/foo.ts.js"));
|
||||
assert(actual[key].startsWith("define("));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -60,9 +62,9 @@ Deno.test({
|
|||
name: "Deno.compile() - pass lib in compiler options",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile(
|
||||
"/foo.ts",
|
||||
"file:///foo.ts",
|
||||
{
|
||||
"/foo.ts": `console.log(document.getElementById("foo"));
|
||||
"file:///foo.ts": `console.log(document.getElementById("foo"));
|
||||
console.log(Deno.args);`,
|
||||
},
|
||||
{
|
||||
|
@ -71,45 +73,37 @@ Deno.test({
|
|||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]);
|
||||
assertEquals(
|
||||
Object.keys(actual).sort(),
|
||||
["file:///foo.ts.js", "file:///foo.ts.js.map"],
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - pass outDir in compiler options",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile(
|
||||
"src/foo.ts",
|
||||
{
|
||||
"src/foo.ts": "console.log('Hello world')",
|
||||
},
|
||||
{
|
||||
outDir: "./lib",
|
||||
},
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["lib/foo.js.map", "lib/foo.js"]);
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.compile() - properly handles .d.ts files",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.compile(
|
||||
"/foo.ts",
|
||||
{
|
||||
"/foo.ts": `console.log(Foo.bar);`,
|
||||
},
|
||||
{
|
||||
types: ["./subdir/foo_types.d.ts"],
|
||||
},
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual);
|
||||
assertEquals(Object.keys(actual), ["/foo.js.map", "/foo.js"]);
|
||||
},
|
||||
});
|
||||
// TODO(@kitsonk) figure the "right way" to restore support for types
|
||||
// Deno.test({
|
||||
// name: "Deno.compile() - properly handles .d.ts files",
|
||||
// async fn() {
|
||||
// const [diagnostics, actual] = await Deno.compile(
|
||||
// "/foo.ts",
|
||||
// {
|
||||
// "/foo.ts": `console.log(Foo.bar);`,
|
||||
// "/foo_types.d.ts": `declare namespace Foo {
|
||||
// const bar: string;
|
||||
// }`,
|
||||
// },
|
||||
// {
|
||||
// types: ["/foo_types.d.ts"],
|
||||
// },
|
||||
// );
|
||||
// assert(diagnostics == null);
|
||||
// assert(actual);
|
||||
// assertEquals(
|
||||
// Object.keys(actual).sort(),
|
||||
// ["file:///foo.ts.js", "file:///file.ts.js.map"],
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.transpileOnly()",
|
||||
|
@ -150,8 +144,7 @@ Deno.test({
|
|||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`__instantiate("foo", false)`));
|
||||
assert(actual.includes(`__exp["bar"]`));
|
||||
assert(actual.includes(`const bar = "bar"`));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -160,26 +153,7 @@ Deno.test({
|
|||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts");
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`__instantiate("mod1", false)`));
|
||||
assert(actual.includes(`__exp["printHello3"]`));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.bundle() - compiler config effects emit",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle(
|
||||
"/foo.ts",
|
||||
{
|
||||
"/foo.ts": `// random comment\nexport * from "./bar.ts";\n`,
|
||||
"/bar.ts": `export const bar = "bar";\n`,
|
||||
},
|
||||
{
|
||||
removeComments: true,
|
||||
},
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(!actual.includes(`random`));
|
||||
assert(actual.length);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -191,22 +165,7 @@ Deno.test({
|
|||
"/bar.js": `export const bar = "bar";\n`,
|
||||
});
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`System.register("bar",`));
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: "Deno.bundle - pre ES2017 uses ES5 loader",
|
||||
async fn() {
|
||||
const [diagnostics, actual] = await Deno.bundle(
|
||||
"/foo.ts",
|
||||
{
|
||||
"/foo.ts": `console.log("hello world!")\n`,
|
||||
},
|
||||
{ target: "es2015" },
|
||||
);
|
||||
assert(diagnostics == null);
|
||||
assert(actual.includes(`var __awaiter = `));
|
||||
assert(actual.includes(`const bar = "bar"`));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -226,8 +185,8 @@ Deno.test({
|
|||
name: "Deno.compile() - SWC diagnostics",
|
||||
async fn() {
|
||||
await assertThrowsAsync(async () => {
|
||||
await Deno.compile("main.js", {
|
||||
"main.js": `
|
||||
await Deno.compile("/main.js", {
|
||||
"/main.js": `
|
||||
export class Foo {
|
||||
constructor() {
|
||||
console.log("foo");
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Deno.compile("main.js", { "main.js": "console.log(foo);" });
|
|
@ -1,4 +0,0 @@
|
|||
Check [WILDCARD]compiler_js_error.ts
|
||||
error: Uncaught (in promise) Error: Error in TS compiler:
|
||||
AssertionError: Unexpected skip of the emit.
|
||||
[WILDCARD]
|
|
@ -3001,12 +3001,6 @@ itest!(deno_doc_import_map {
|
|||
output: "doc/use_import_map.out",
|
||||
});
|
||||
|
||||
itest!(compiler_js_error {
|
||||
args: "run --unstable compiler_js_error.ts",
|
||||
output: "compiler_js_error.ts.out",
|
||||
exit_code: 1,
|
||||
});
|
||||
|
||||
itest!(import_file_with_colon {
|
||||
args: "run --quiet --reload import_file_with_colon.ts",
|
||||
output: "import_file_with_colon.ts.out",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const [errors, program] = await Deno.compile(
|
||||
"main.ts",
|
||||
"/main.ts",
|
||||
{
|
||||
"main.ts":
|
||||
"/main.ts":
|
||||
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
|
||||
},
|
||||
{
|
||||
|
@ -11,4 +11,4 @@ const [errors, program] = await Deno.compile(
|
|||
);
|
||||
|
||||
console.log(errors);
|
||||
console.log(Object.keys(program));
|
||||
console.log(Object.keys(program).sort());
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
undefined
|
||||
[ "main.js.map", "main.js" ]
|
||||
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const [errors, program] = await Deno.compile(
|
||||
"main.ts",
|
||||
"/main.ts",
|
||||
{
|
||||
"main.ts": `document.getElementById("foo");`,
|
||||
"/main.ts": `document.getElementById("foo");`,
|
||||
},
|
||||
{
|
||||
lib: ["dom", "esnext"],
|
||||
|
@ -9,4 +9,4 @@ const [errors, program] = await Deno.compile(
|
|||
);
|
||||
|
||||
console.log(errors);
|
||||
console.log(Object.keys(program));
|
||||
console.log(Object.keys(program).sort());
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
undefined
|
||||
[ "main.js.map", "main.js" ]
|
||||
[ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]
|
||||
|
|
1005
cli/tsc.rs
1005
cli/tsc.rs
File diff suppressed because it is too large
Load diff
|
@ -118,9 +118,6 @@ delete Object.prototype.__proto__;
|
|||
return core.decode(sourceCodeBytes);
|
||||
}
|
||||
|
||||
// Constants used by `normalizeString` and `resolvePath`
|
||||
const CHAR_DOT = 46; /* . */
|
||||
const CHAR_FORWARD_SLASH = 47; /* / */
|
||||
// Using incremental compile APIs requires that all
|
||||
// paths must be either relative or absolute. Since
|
||||
// analysis in Rust operates on fully resolved URLs,
|
||||
|
@ -218,18 +215,6 @@ delete Object.prototype.__proto__;
|
|||
*/
|
||||
const RESOLVED_SPECIFIER_CACHE = new Map();
|
||||
|
||||
function parseCompilerOptions(compilerOptions) {
|
||||
const { options, errors } = ts.convertCompilerOptionsFromJson(
|
||||
compilerOptions,
|
||||
"",
|
||||
"tsconfig.json",
|
||||
);
|
||||
return {
|
||||
options,
|
||||
diagnostics: errors.length ? errors : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
class SourceFile {
|
||||
constructor(json) {
|
||||
this.processed = false;
|
||||
|
@ -541,95 +526,6 @@ delete Object.prototype.__proto__;
|
|||
host,
|
||||
});
|
||||
|
||||
// This function is called only during snapshotting process
|
||||
const SYSTEM_LOADER = getAsset("system_loader.js");
|
||||
const SYSTEM_LOADER_ES5 = getAsset("system_loader_es5.js");
|
||||
|
||||
function buildLocalSourceFileCache(sourceFileMap) {
|
||||
for (const entry of Object.values(sourceFileMap)) {
|
||||
assert(entry.sourceCode.length > 0);
|
||||
SourceFile.addToCache({
|
||||
url: entry.url,
|
||||
filename: entry.url,
|
||||
mediaType: entry.mediaType,
|
||||
sourceCode: entry.sourceCode,
|
||||
versionHash: entry.versionHash,
|
||||
});
|
||||
|
||||
for (const importDesc of entry.imports) {
|
||||
let mappedUrl = importDesc.resolvedSpecifier;
|
||||
const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
|
||||
assert(importedFile);
|
||||
const isJsOrJsx = importedFile.mediaType === MediaType.JavaScript ||
|
||||
importedFile.mediaType === MediaType.JSX;
|
||||
// If JS or JSX perform substitution for types if available
|
||||
if (isJsOrJsx) {
|
||||
// @deno-types has highest precedence, followed by
|
||||
// X-TypeScript-Types header
|
||||
if (importDesc.resolvedTypeDirective) {
|
||||
mappedUrl = importDesc.resolvedTypeDirective;
|
||||
} else if (importedFile.typeHeaders.length > 0) {
|
||||
const typeHeaders = importedFile.typeHeaders[0];
|
||||
mappedUrl = typeHeaders.resolvedSpecifier;
|
||||
} else if (importedFile.typesDirectives.length > 0) {
|
||||
const typeDirective = importedFile.typesDirectives[0];
|
||||
mappedUrl = typeDirective.resolvedSpecifier;
|
||||
}
|
||||
}
|
||||
|
||||
mappedUrl = mappedUrl.replace("memory://", "");
|
||||
SourceFile.cacheResolvedUrl(mappedUrl, importDesc.specifier, entry.url);
|
||||
}
|
||||
for (const fileRef of entry.referencedFiles) {
|
||||
SourceFile.cacheResolvedUrl(
|
||||
fileRef.resolvedSpecifier.replace("memory://", ""),
|
||||
fileRef.specifier,
|
||||
entry.url,
|
||||
);
|
||||
}
|
||||
for (const fileRef of entry.libDirectives) {
|
||||
SourceFile.cacheResolvedUrl(
|
||||
fileRef.resolvedSpecifier.replace("memory://", ""),
|
||||
fileRef.specifier,
|
||||
entry.url,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Warning! The values in this enum are duplicated in `cli/msg.rs`
|
||||
// Update carefully!
|
||||
const CompilerRequestType = {
|
||||
RuntimeCompile: 2,
|
||||
RuntimeBundle: 3,
|
||||
};
|
||||
|
||||
function createBundleWriteFile(state) {
|
||||
return function writeFile(_fileName, data, sourceFiles) {
|
||||
assert(sourceFiles != null);
|
||||
assert(state.options);
|
||||
// we only support single root names for bundles
|
||||
assert(state.rootNames.length === 1);
|
||||
state.bundleOutput = buildBundle(
|
||||
state.rootNames[0],
|
||||
data,
|
||||
sourceFiles,
|
||||
state.options.target ?? ts.ScriptTarget.ESNext,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function createRuntimeCompileWriteFile(state) {
|
||||
return function writeFile(fileName, data, sourceFiles) {
|
||||
assert(sourceFiles);
|
||||
assert(sourceFiles.length === 1);
|
||||
state.emitMap[fileName] = {
|
||||
filename: sourceFiles[0].fileName,
|
||||
contents: data,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const IGNORED_DIAGNOSTICS = [
|
||||
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
|
||||
// not a module.
|
||||
|
@ -674,7 +570,6 @@ delete Object.prototype.__proto__;
|
|||
|
||||
function performanceStart() {
|
||||
stats.length = 0;
|
||||
// TODO(kitsonk) replace with performance.mark() when landed
|
||||
statsStart = new Date();
|
||||
ts.performance.enable();
|
||||
}
|
||||
|
@ -716,317 +611,6 @@ delete Object.prototype.__proto__;
|
|||
return stats;
|
||||
}
|
||||
|
||||
function normalizeString(path) {
|
||||
let res = "";
|
||||
let lastSegmentLength = 0;
|
||||
let lastSlash = -1;
|
||||
let dots = 0;
|
||||
let code;
|
||||
for (let i = 0, len = path.length; i <= len; ++i) {
|
||||
if (i < len) code = path.charCodeAt(i);
|
||||
else if (code === CHAR_FORWARD_SLASH) break;
|
||||
else code = CHAR_FORWARD_SLASH;
|
||||
|
||||
if (code === CHAR_FORWARD_SLASH) {
|
||||
if (lastSlash === i - 1 || dots === 1) {
|
||||
// NOOP
|
||||
} else if (lastSlash !== i - 1 && dots === 2) {
|
||||
if (
|
||||
res.length < 2 ||
|
||||
lastSegmentLength !== 2 ||
|
||||
res.charCodeAt(res.length - 1) !== CHAR_DOT ||
|
||||
res.charCodeAt(res.length - 2) !== CHAR_DOT
|
||||
) {
|
||||
if (res.length > 2) {
|
||||
const lastSlashIndex = res.lastIndexOf("/");
|
||||
if (lastSlashIndex === -1) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
} else {
|
||||
res = res.slice(0, lastSlashIndex);
|
||||
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
} else if (res.length === 2 || res.length === 1) {
|
||||
res = "";
|
||||
lastSegmentLength = 0;
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (res.length > 0) res += "/" + path.slice(lastSlash + 1, i);
|
||||
else res = path.slice(lastSlash + 1, i);
|
||||
lastSegmentLength = i - lastSlash - 1;
|
||||
}
|
||||
lastSlash = i;
|
||||
dots = 0;
|
||||
} else if (code === CHAR_DOT && dots !== -1) {
|
||||
++dots;
|
||||
} else {
|
||||
dots = -1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function commonPath(paths, sep = "/") {
|
||||
const [first = "", ...remaining] = paths;
|
||||
if (first === "" || remaining.length === 0) {
|
||||
return first.substring(0, first.lastIndexOf(sep) + 1);
|
||||
}
|
||||
const parts = first.split(sep);
|
||||
|
||||
let endOfPrefix = parts.length;
|
||||
for (const path of remaining) {
|
||||
const compare = path.split(sep);
|
||||
for (let i = 0; i < endOfPrefix; i++) {
|
||||
if (compare[i] !== parts[i]) {
|
||||
endOfPrefix = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (endOfPrefix === 0) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
const prefix = parts.slice(0, endOfPrefix).join(sep);
|
||||
return prefix.endsWith(sep) ? prefix : `${prefix}${sep}`;
|
||||
}
|
||||
|
||||
let rootExports;
|
||||
|
||||
function normalizeUrl(rootName) {
|
||||
const match = /^(\S+:\/{2,3})(.+)$/.exec(rootName);
|
||||
if (match) {
|
||||
const [, protocol, path] = match;
|
||||
return `${protocol}${normalizeString(path)}`;
|
||||
} else {
|
||||
return rootName;
|
||||
}
|
||||
}
|
||||
|
||||
function buildBundle(rootName, data, sourceFiles, target) {
|
||||
// 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, "");
|
||||
// If one of the modules requires support for top-level-await, TypeScript will
|
||||
// emit the execute function as an async function. When this is the case we
|
||||
// need to bubble up the TLA to the instantiation, otherwise we instantiate
|
||||
// synchronously.
|
||||
const hasTla = data.match(/execute:\sasync\sfunction\s/);
|
||||
let instantiate;
|
||||
if (rootExports && rootExports.length) {
|
||||
instantiate = hasTla
|
||||
? `const __exp = await __instantiate("${rootName}", true);\n`
|
||||
: `const __exp = __instantiate("${rootName}", false);\n`;
|
||||
for (const rootExport of rootExports) {
|
||||
if (rootExport === "default") {
|
||||
instantiate += `export default __exp["${rootExport}"];\n`;
|
||||
} else {
|
||||
instantiate +=
|
||||
`export const ${rootExport} = __exp["${rootExport}"];\n`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instantiate = hasTla
|
||||
? `await __instantiate("${rootName}", true);\n`
|
||||
: `__instantiate("${rootName}", false);\n`;
|
||||
}
|
||||
const es5Bundle = target === ts.ScriptTarget.ES3 ||
|
||||
target === ts.ScriptTarget.ES5 ||
|
||||
target === ts.ScriptTarget.ES2015 ||
|
||||
target === ts.ScriptTarget.ES2016;
|
||||
return `${
|
||||
es5Bundle ? SYSTEM_LOADER_ES5 : SYSTEM_LOADER
|
||||
}\n${data}\n${instantiate}`;
|
||||
}
|
||||
|
||||
function setRootExports(program, rootModule) {
|
||||
// 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.Class ||
|
||||
!(
|
||||
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());
|
||||
}
|
||||
|
||||
function runtimeCompile(request) {
|
||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
||||
|
||||
debug(">>> runtime compile start", {
|
||||
rootNames,
|
||||
});
|
||||
|
||||
// if there are options, convert them into TypeScript compiler options,
|
||||
// and resolve any external file references
|
||||
const result = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
const options = result.options;
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
buildLocalSourceFileCache(sourceFileMap);
|
||||
|
||||
const state = {
|
||||
rootNames,
|
||||
emitMap: {},
|
||||
};
|
||||
legacyHostState.target = target;
|
||||
legacyHostState.writeFile = createRuntimeCompileWriteFile(state);
|
||||
const program = ts.createProgram({
|
||||
rootNames,
|
||||
options,
|
||||
host,
|
||||
});
|
||||
|
||||
const diagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.filter(({ code }) =>
|
||||
!IGNORED_DIAGNOSTICS.includes(code) &&
|
||||
!IGNORED_COMPILE_DIAGNOSTICS.includes(code)
|
||||
);
|
||||
|
||||
const emitResult = program.emit();
|
||||
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
|
||||
|
||||
debug("<<< runtime compile finish", {
|
||||
rootNames,
|
||||
emitMap: Object.keys(state.emitMap),
|
||||
});
|
||||
|
||||
const maybeDiagnostics = diagnostics.length
|
||||
? fromTypeScriptDiagnostic(diagnostics)
|
||||
: [];
|
||||
|
||||
return {
|
||||
diagnostics: maybeDiagnostics,
|
||||
emitMap: state.emitMap,
|
||||
};
|
||||
}
|
||||
|
||||
function runtimeBundle(request) {
|
||||
const { compilerOptions, rootNames, target, sourceFileMap } = request;
|
||||
|
||||
debug(">>> runtime bundle start", {
|
||||
rootNames,
|
||||
});
|
||||
|
||||
// if there are options, convert them into TypeScript compiler options,
|
||||
// and resolve any external file references
|
||||
const result = parseCompilerOptions(
|
||||
compilerOptions,
|
||||
);
|
||||
const options = result.options;
|
||||
// TODO(bartlomieju): this options is excluded by `ts.convertCompilerOptionsFromJson`
|
||||
// however stuff breaks if it's not passed (type_directives_js_main.js, compiler_js_error.ts)
|
||||
options.allowNonTsExtensions = true;
|
||||
|
||||
buildLocalSourceFileCache(sourceFileMap);
|
||||
|
||||
const state = {
|
||||
rootNames,
|
||||
bundleOutput: undefined,
|
||||
};
|
||||
|
||||
legacyHostState.target = target;
|
||||
legacyHostState.writeFile = createBundleWriteFile(state);
|
||||
state.options = options;
|
||||
|
||||
const program = ts.createProgram({
|
||||
rootNames,
|
||||
options,
|
||||
host,
|
||||
});
|
||||
|
||||
setRootExports(program, rootNames[0]);
|
||||
const diagnostics = ts
|
||||
.getPreEmitDiagnostics(program)
|
||||
.filter(({ code }) => !IGNORED_DIAGNOSTICS.includes(code));
|
||||
|
||||
const emitResult = program.emit();
|
||||
|
||||
assert(emitResult.emitSkipped === false, "Unexpected skip of the emit.");
|
||||
|
||||
debug("<<< runtime bundle finish", {
|
||||
rootNames,
|
||||
});
|
||||
|
||||
const maybeDiagnostics = diagnostics.length
|
||||
? fromTypeScriptDiagnostic(diagnostics)
|
||||
: [];
|
||||
|
||||
return {
|
||||
diagnostics: maybeDiagnostics,
|
||||
output: state.bundleOutput,
|
||||
};
|
||||
}
|
||||
|
||||
function opCompilerRespond(msg) {
|
||||
core.jsonOpSync("op_compiler_respond", msg);
|
||||
}
|
||||
|
||||
function tsCompilerOnMessage(msg) {
|
||||
const request = msg.data;
|
||||
switch (request.type) {
|
||||
case CompilerRequestType.RuntimeCompile: {
|
||||
const result = runtimeCompile(request);
|
||||
opCompilerRespond(result);
|
||||
break;
|
||||
}
|
||||
case CompilerRequestType.RuntimeBundle: {
|
||||
const result = runtimeBundle(request);
|
||||
opCompilerRespond(result);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`!!! unhandled CompilerRequestType: ${request.type} (${
|
||||
CompilerRequestType[request.type]
|
||||
})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} Request
|
||||
* @property {Record<string, any>} config
|
||||
|
@ -1094,6 +678,4 @@ delete Object.prototype.__proto__;
|
|||
|
||||
globalThis.startup = startup;
|
||||
globalThis.exec = exec;
|
||||
// TODO(@kitsonk) remove when converted from legacy tsc
|
||||
globalThis.tsCompilerOnMessage = tsCompilerOnMessage;
|
||||
})(this);
|
||||
|
|
|
@ -52,37 +52,49 @@ impl fmt::Display for IgnoredCompilerOptions {
|
|||
/// A static slice of all the compiler options that should be ignored that
|
||||
/// either have no effect on the compilation or would cause the emit to not work
|
||||
/// in Deno.
|
||||
const IGNORED_COMPILER_OPTIONS: [&str; 10] = [
|
||||
const IGNORED_COMPILER_OPTIONS: &[&str] = &[
|
||||
"allowSyntheticDefaultImports",
|
||||
"allowUmdGlobalAccess",
|
||||
"baseUrl",
|
||||
"declaration",
|
||||
"declarationMap",
|
||||
"downlevelIteration",
|
||||
"esModuleInterop",
|
||||
"emitDeclarationOnly",
|
||||
"importHelpers",
|
||||
"inlineSourceMap",
|
||||
"inlineSources",
|
||||
// TODO(nayeemrmn): Add "isolatedModules" here for 1.6.0.
|
||||
"module",
|
||||
"noEmitHelpers",
|
||||
"noLib",
|
||||
"noResolve",
|
||||
"outDir",
|
||||
"paths",
|
||||
"preserveConstEnums",
|
||||
"reactNamespace",
|
||||
"rootDir",
|
||||
"rootDirs",
|
||||
"skipLibCheck",
|
||||
"sourceMap",
|
||||
"sourceRoot",
|
||||
"target",
|
||||
"types",
|
||||
"useDefineForClassFields",
|
||||
];
|
||||
|
||||
const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [
|
||||
"allowUmdGlobalAccess",
|
||||
const IGNORED_RUNTIME_COMPILER_OPTIONS: &[&str] = &[
|
||||
"assumeChangesOnlyAffectDirectDependencies",
|
||||
"baseUrl",
|
||||
"build",
|
||||
"charset",
|
||||
"composite",
|
||||
"declaration",
|
||||
"declarationMap",
|
||||
"diagnostics",
|
||||
"downlevelIteration",
|
||||
"disableSizeLimit",
|
||||
"emitBOM",
|
||||
"emitDeclarationOnly",
|
||||
"extendedDiagnostics",
|
||||
"forceConsistentCasingInFileNames",
|
||||
"generateCpuProfile",
|
||||
"help",
|
||||
"importHelpers",
|
||||
"incremental",
|
||||
"init",
|
||||
"listEmittedFiles",
|
||||
|
@ -92,29 +104,21 @@ const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [
|
|||
"moduleResolution",
|
||||
"newLine",
|
||||
"noEmit",
|
||||
"noEmitHelpers",
|
||||
"noEmitOnError",
|
||||
"noResolve",
|
||||
"out",
|
||||
"outDir",
|
||||
"outFile",
|
||||
"paths",
|
||||
"preserveSymlinks",
|
||||
"preserveWatchOutput",
|
||||
"pretty",
|
||||
"project",
|
||||
"resolveJsonModule",
|
||||
"rootDir",
|
||||
"rootDirs",
|
||||
"showConfig",
|
||||
"skipDefaultLibCheck",
|
||||
"skipLibCheck",
|
||||
"sourceRoot",
|
||||
"stripInternal",
|
||||
"traceResolution",
|
||||
"tsBuildInfoFile",
|
||||
"types",
|
||||
"typeRoots",
|
||||
"useDefineForClassFields",
|
||||
"version",
|
||||
"watch",
|
||||
];
|
||||
|
@ -170,12 +174,6 @@ struct TSConfigJson {
|
|||
type_acquisition: Option<Value>,
|
||||
}
|
||||
|
||||
pub fn parse_raw_config(config_text: &str) -> Result<Value, AnyError> {
|
||||
assert!(!config_text.is_empty());
|
||||
let jsonc = jsonc_parser::parse_to_value(config_text)?.unwrap();
|
||||
Ok(jsonc_to_serde(jsonc))
|
||||
}
|
||||
|
||||
fn parse_compiler_options(
|
||||
compiler_options: &HashMap<String, Value>,
|
||||
maybe_path: Option<PathBuf>,
|
||||
|
@ -394,18 +392,6 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_raw_config() {
|
||||
let invalid_config_text = r#"{
|
||||
"compilerOptions": {
|
||||
// comments are allowed
|
||||
}"#;
|
||||
let errbox = parse_raw_config(invalid_config_text).unwrap_err();
|
||||
assert!(errbox
|
||||
.to_string()
|
||||
.starts_with("Unterminated object on line 1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tsconfig_as_bytes() {
|
||||
let mut tsconfig1 = TsConfig::new(json!({
|
||||
|
|
Loading…
Reference in a new issue