1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

refactor(cli): migrate runtime compile/bundle to new infrastructure (#8192)

Fixes #8060
This commit is contained in:
Kitson Kelly 2020-11-02 13:51:56 +11:00 committed by GitHub
parent 3558769d46
commit fdcc78500c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 852 additions and 2770 deletions

View file

@ -2,9 +2,11 @@
use crate::colors; 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 regex::Regex;
use serde::Deserialize;
use serde::Deserializer;
use std::error::Error; use std::error::Error;
use std::fmt; 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 { impl From<i64> for DiagnosticCategory {
fn from(value: i64) -> Self { fn from(value: i64) -> Self {
match value { 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")] #[serde(rename_all = "camelCase")]
pub struct DiagnosticMessageChain { pub struct DiagnosticMessageChain {
message_text: String, 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")] #[serde(rename_all = "camelCase")]
pub struct Position { pub struct Position {
pub line: u64, pub line: u64,
pub character: u64, pub character: u64,
} }
#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Diagnostic { pub struct Diagnostic {
pub category: DiagnosticCategory, 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 { impl fmt::Display for Diagnostics {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut i = 0; let mut i = 0;

View file

@ -303,9 +303,6 @@ declare namespace Deno {
/** Provide full support for iterables in `for..of`, spread and /** Provide full support for iterables in `for..of`, spread and
* destructuring when targeting ES5 or ES3. Defaults to `false`. */ * destructuring when targeting ES5 or ES3. Defaults to `false`. */
downlevelIteration?: boolean; 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`. */ /** Only emit `.d.ts` declaration files. Defaults to `false`. */
emitDeclarationOnly?: boolean; emitDeclarationOnly?: boolean;
/** Emit design-type metadata for decorated declarations in source. See issue /** 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 * ecosystem compatibility and enable `allowSyntheticDefaultImports` for type
* system compatibility. Defaults to `true`. */ * system compatibility. Defaults to `true`. */
esModuleInterop?: boolean; esModuleInterop?: boolean;
/** Enables experimental support for ES decorators. Defaults to `false`. */ /** Enables experimental support for ES decorators. Defaults to `true`. */
experimentalDecorators?: boolean; 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. /** Emit a single file with source maps instead of having a separate file.
* Defaults to `false`. */ * Defaults to `false`. */
inlineSourceMap?: boolean; inlineSourceMap?: boolean;
@ -325,7 +325,7 @@ declare namespace Deno {
* `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */ * `inlineSourceMap` or `sourceMap` to be set. Defaults to `false`. */
inlineSources?: boolean; inlineSources?: boolean;
/** Perform additional checks to ensure that transpile only would be safe. /** Perform additional checks to ensure that transpile only would be safe.
* Defaults to `false`. */ * Defaults to `true`. */
isolatedModules?: boolean; isolatedModules?: boolean;
/** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`. /** Support JSX in `.tsx` files: `"react"`, `"preserve"`, `"react-native"`.
* Defaults to `"react"`. */ * Defaults to `"react"`. */
@ -333,12 +333,12 @@ declare namespace Deno {
/** Specify the JSX factory function to use when targeting react JSX emit, /** Specify the JSX factory function to use when targeting react JSX emit,
* e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */ * e.g. `React.createElement` or `h`. Defaults to `React.createElement`. */
jsxFactory?: string; 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 /** Resolve keyof to string valued property names only (no numbers or
* symbols). Defaults to `false`. */ * symbols). Defaults to `false`. */
keyofStringsOnly?: string; 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, /** List of library files to be included in the compilation. If omitted,
* then the Deno main runtime libs are used. */ * then the Deno main runtime libs are used. */
lib?: string[]; lib?: string[];
@ -389,10 +389,6 @@ declare namespace Deno {
noUnusedLocals?: boolean; noUnusedLocals?: boolean;
/** Report errors on unused parameters. Defaults to `false`. */ /** Report errors on unused parameters. Defaults to `false`. */
noUnusedParameters?: boolean; 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 /** List of path mapping entries for module names to locations relative to the
* `baseUrl`. Defaults to `undefined`. */ * `baseUrl`. Defaults to `undefined`. */
paths?: Record<string, string[]>; paths?: Record<string, string[]>;
@ -402,8 +398,6 @@ declare namespace Deno {
/** Remove all comments except copy-right header comments beginning with /** Remove all comments except copy-right header comments beginning with
* `/*!`. Defaults to `true`. */ * `/*!`. Defaults to `true`. */
removeComments?: boolean; 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 /** Specifies the root directory of input files. Only use to control the
* output directory structure with `outDir`. Defaults to `undefined`. */ * output directory structure with `outDir`. Defaults to `undefined`. */
rootDir?: string; rootDir?: string;
@ -418,6 +412,8 @@ declare namespace Deno {
* specified will be embedded in the sourceMap to direct the debugger where * specified will be embedded in the sourceMap to direct the debugger where
* the source files will be located. Defaults to `undefined`. */ * the source files will be located. Defaults to `undefined`. */
sourceRoot?: string; sourceRoot?: string;
/** Skip type checking of all declaration files (`*.d.ts`). */
skipLibCheck?: boolean;
/** Enable all strict type checking options. Enabling `strict` enables /** Enable all strict type checking options. Enabling `strict` enables
* `noImplicitAny`, `noImplicitThis`, `alwaysStrict`, `strictBindCallApply`, * `noImplicitAny`, `noImplicitThis`, `alwaysStrict`, `strictBindCallApply`,
* `strictNullChecks`, `strictFunctionTypes` and * `strictNullChecks`, `strictFunctionTypes` and
@ -472,6 +468,9 @@ declare namespace Deno {
* ``` * ```
*/ */
types?: string[]; types?: string[];
/** Emit class fields with ECMAScript-standard semantics. Defaults to
* `false`. */
useDefineForClassFields?: boolean;
} }
/** **UNSTABLE**: new API, yet to be vetted. /** **UNSTABLE**: new API, yet to be vetted.

View file

@ -35,7 +35,6 @@ mod lint;
mod lockfile; mod lockfile;
mod media_type; mod media_type;
mod metrics; mod metrics;
mod module_graph;
mod module_graph2; mod module_graph2;
mod module_loader; mod module_loader;
mod op_fetch_asset; mod op_fetch_asset;
@ -50,7 +49,6 @@ mod specifier_handler;
mod test_runner; mod test_runner;
mod text_encoding; mod text_encoding;
mod tokio_util; mod tokio_util;
mod tsc;
mod tsc2; mod tsc2;
mod tsc_config; mod tsc_config;
mod upgrade; mod upgrade;
@ -242,6 +240,11 @@ async fn cache_command(
flags: Flags, flags: Flags,
files: Vec<String>, files: Vec<String>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let lib = if flags.unstable {
module_graph2::TypeLib::UnstableDenoWindow
} else {
module_graph2::TypeLib::DenoWindow
};
let program_state = ProgramState::new(flags)?; let program_state = ProgramState::new(flags)?;
for file in files { for file in files {
@ -249,7 +252,7 @@ async fn cache_command(
program_state program_state
.prepare_module_load( .prepare_module_load(
specifier, specifier,
tsc::TargetLib::Main, lib.clone(),
Permissions::allow_all(), Permissions::allow_all(),
false, false,
program_state.maybe_import_map.clone(), program_state.maybe_import_map.clone(),
@ -343,8 +346,7 @@ async fn bundle_command(
module_graph2::TypeLib::DenoWindow module_graph2::TypeLib::DenoWindow
}; };
let graph = graph.clone(); let graph = graph.clone();
let (stats, diagnostics, maybe_ignored_options) = let result_info = graph.check(module_graph2::CheckOptions {
graph.check(module_graph2::CheckOptions {
debug, debug,
emit: false, emit: false,
lib, lib,
@ -352,12 +354,12 @@ async fn bundle_command(
reload: flags.reload, reload: flags.reload,
})?; })?;
debug!("{}", stats); debug!("{}", result_info.stats);
if let Some(ignored_options) = maybe_ignored_options { if let Some(ignored_options) = result_info.maybe_ignored_options {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
if !diagnostics.is_empty() { if !result_info.diagnostics.is_empty() {
return Err(generic_error(diagnostics.to_string())); return Err(generic_error(result_info.diagnostics.to_string()));
} }
} }

View file

@ -1,5 +1,6 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use deno_core::ModuleSpecifier;
use serde::Serialize; use serde::Serialize;
use serde::Serializer; use serde::Serializer;
use std::fmt; 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 { impl Default for MediaType {
fn default() -> Self { fn default() -> Self {
MediaType::Unknown MediaType::Unknown

View file

@ -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,
},
},
]
);
}

View file

@ -1,9 +1,9 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::ast;
use crate::ast::parse; use crate::ast::parse;
use crate::ast::transpile_module; use crate::ast::transpile_module;
use crate::ast::BundleHook; use crate::ast::BundleHook;
use crate::ast::EmitOptions;
use crate::ast::Location; use crate::ast::Location;
use crate::ast::ParsedModule; use crate::ast::ParsedModule;
use crate::colors; use crate::colors;
@ -22,8 +22,7 @@ use crate::specifier_handler::DependencyMap;
use crate::specifier_handler::Emit; use crate::specifier_handler::Emit;
use crate::specifier_handler::FetchFuture; use crate::specifier_handler::FetchFuture;
use crate::specifier_handler::SpecifierHandler; use crate::specifier_handler::SpecifierHandler;
use crate::tsc2::exec; use crate::tsc2;
use crate::tsc2::Request;
use crate::tsc_config::IgnoredCompilerOptions; use crate::tsc_config::IgnoredCompilerOptions;
use crate::tsc_config::TsConfig; use crate::tsc_config::TsConfig;
use crate::version; use crate::version;
@ -35,6 +34,7 @@ use deno_core::futures::stream::StreamExt;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde::Serializer; use deno_core::serde::Serializer;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_json::Value;
use deno_core::ModuleResolutionError; use deno_core::ModuleResolutionError;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use regex::Regex; use regex::Regex;
@ -121,13 +121,13 @@ impl Error for GraphError {}
struct BundleLoader<'a> { struct BundleLoader<'a> {
cm: Rc<swc_common::SourceMap>, cm: Rc<swc_common::SourceMap>,
graph: &'a Graph2, graph: &'a Graph2,
emit_options: &'a EmitOptions, emit_options: &'a ast::EmitOptions,
} }
impl<'a> BundleLoader<'a> { impl<'a> BundleLoader<'a> {
pub fn new( pub fn new(
graph: &'a Graph2, graph: &'a Graph2,
emit_options: &'a EmitOptions, emit_options: &'a ast::EmitOptions,
cm: Rc<swc_common::SourceMap>, cm: Rc<swc_common::SourceMap>,
) -> Self { ) -> Self {
BundleLoader { 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)>); pub struct Stats(pub Vec<(String, u128)>);
impl<'de> Deserialize<'de> for Stats { 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)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum TypeLib { pub enum TypeLib {
DenoWindow, DenoWindow,
@ -539,7 +556,11 @@ impl Serialize for TypeLib {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct BundleOptions { pub struct BundleOptions {
/// If `true` then debug logging will be output from the isolate.
pub debug: bool, 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>, pub maybe_config_path: Option<String>,
} }
@ -560,6 +581,35 @@ pub struct CheckOptions {
pub reload: bool, 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. /// A structure which provides options when transpiling modules.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TranspileOptions { pub struct TranspileOptions {
@ -647,47 +697,8 @@ impl Graph2 {
})); }));
let maybe_ignored_options = let maybe_ignored_options =
ts_config.merge_tsconfig(options.maybe_config_path)?; 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 let s = self.emit_bundle(&root_specifier, &ts_config.into())?;
.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 stats = Stats(vec![ let stats = Stats(vec![
("Files".to_string(), self.modules.len() as u128), ("Files".to_string(), self.modules.len() as u128),
("Total time".to_string(), start.elapsed().as_millis()), ("Total time".to_string(), start.elapsed().as_millis()),
@ -697,11 +708,7 @@ impl Graph2 {
} }
/// Type check the module graph, corresponding to the options provided. /// Type check the module graph, corresponding to the options provided.
pub fn check( pub fn check(self, options: CheckOptions) -> Result<ResultInfo, AnyError> {
self,
options: CheckOptions,
) -> Result<(Stats, Diagnostics, Option<IgnoredCompilerOptions>), AnyError>
{
let mut config = TsConfig::new(json!({ let mut config = TsConfig::new(json!({
"allowJs": true, "allowJs": true,
// TODO(@kitsonk) is this really needed? // TODO(@kitsonk) is this really needed?
@ -745,11 +752,10 @@ impl Graph2 {
&& (!options.reload || self.roots_dynamic)) && (!options.reload || self.roots_dynamic))
{ {
debug!("graph does not need to be checked or emitted."); debug!("graph does not need to be checked or emitted.");
return Ok(( return Ok(ResultInfo {
Stats(Vec::new()),
Diagnostics::default(),
maybe_ignored_options, maybe_ignored_options,
)); ..Default::default()
});
} }
// TODO(@kitsonk) not totally happy with this here, but this is the first // TODO(@kitsonk) not totally happy with this here, but this is the first
@ -760,26 +766,15 @@ impl Graph2 {
info!("{} {}", colors::green("Check"), specifier); info!("{} {}", colors::green("Check"), specifier);
} }
let root_names: Vec<(ModuleSpecifier, MediaType)> = self let root_names = self.get_root_names();
.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 maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone(); let maybe_tsbuildinfo = self.maybe_tsbuildinfo.clone();
let hash_data = let hash_data =
vec![config.as_bytes(), version::DENO.as_bytes().to_owned()]; vec![config.as_bytes(), version::DENO.as_bytes().to_owned()];
let graph = Rc::new(RefCell::new(self)); let graph = Rc::new(RefCell::new(self));
let response = exec( let response = tsc2::exec(
js::compiler_isolate_init(), js::compiler_isolate_init(),
Request { tsc2::Request {
config: config.clone(), config: config.clone(),
debug: options.debug, debug: options.debug,
graph: graph.clone(), graph: graph.clone(),
@ -837,7 +832,11 @@ impl Graph2 {
} }
graph.flush()?; 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 { fn contains_module(&self, specifier: &ModuleSpecifier) -> bool {
@ -845,6 +844,165 @@ impl Graph2 {
self.modules.contains_key(s) 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 /// Update the handler with any modules that are marked as _dirty_ and update
/// any build info if present. /// any build info if present.
fn flush(&mut self) -> Result<(), AnyError> { fn flush(&mut self) -> Result<(), AnyError> {
@ -963,22 +1121,6 @@ impl Graph2 {
self.modules.get(s) 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( fn get_module_mut(
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
@ -993,6 +1135,41 @@ impl Graph2 {
self.modules.get_mut(s) 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 /// Return a structure which provides information about the module graph and
/// the relationship of the modules in the graph. This structure is used to /// the relationship of the modules in the graph. This structure is used to
/// provide information for the `info` subcommand. /// provide information for the `info` subcommand.
@ -1209,7 +1386,7 @@ impl Graph2 {
let maybe_ignored_options = let maybe_ignored_options =
ts_config.merge_tsconfig(options.maybe_config_path)?; 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 mut emit_count: u128 = 0;
let config = ts_config.as_bytes(); let config = ts_config.as_bytes();
@ -1434,12 +1611,25 @@ impl GraphBuilder2 {
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::specifier_handler::MemoryHandler;
use deno_core::futures::future; use deno_core::futures::future;
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Mutex; 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 /// This is a testing mock for `SpecifierHandler` that uses a special file
/// system renaming to mock local and remote modules as well as provides /// system renaming to mock local and remote modules as well as provides
/// "spies" for the critical methods for testing purposes. /// "spies" for the critical methods for testing purposes.
@ -1465,20 +1655,7 @@ pub mod tests {
.replace("://", "_") .replace("://", "_")
.replace("/", "-"); .replace("/", "-");
let source_path = self.fixtures.join(specifier_text); let source_path = self.fixtures.join(specifier_text);
let media_type = match source_path.extension().unwrap().to_str().unwrap() let media_type = MediaType::from(&source_path);
{
"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 source = fs::read_to_string(&source_path)?; let source = fs::read_to_string(&source_path)?;
let is_remote = specifier.as_url().scheme() != "file"; let is_remote = specifier.as_url().scheme() != "file";
@ -1572,6 +1749,24 @@ pub mod tests {
(builder.get_graph(), handler) (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] #[test]
fn test_get_version() { fn test_get_version() {
let doc_a = "console.log(42);"; let doc_a = "console.log(42);";
@ -1694,7 +1889,7 @@ pub mod tests {
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts") ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module"); .expect("could not resolve module");
let (graph, handler) = setup(specifier).await; let (graph, handler) = setup(specifier).await;
let (stats, diagnostics, maybe_ignored_options) = graph let result_info = graph
.check(CheckOptions { .check(CheckOptions {
debug: false, debug: false,
emit: true, emit: true,
@ -1703,9 +1898,9 @@ pub mod tests {
reload: false, reload: false,
}) })
.expect("should have checked"); .expect("should have checked");
assert!(maybe_ignored_options.is_none()); assert!(result_info.maybe_ignored_options.is_none());
assert_eq!(stats.0.len(), 12); assert_eq!(result_info.stats.0.len(), 12);
assert!(diagnostics.is_empty()); assert!(result_info.diagnostics.is_empty());
let h = handler.borrow(); let h = handler.borrow();
assert_eq!(h.cache_calls.len(), 2); assert_eq!(h.cache_calls.len(), 2);
assert_eq!(h.tsbuildinfo_calls.len(), 1); assert_eq!(h.tsbuildinfo_calls.len(), 1);
@ -1717,7 +1912,7 @@ pub mod tests {
ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts") ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module"); .expect("could not resolve module");
let (graph, handler) = setup(specifier).await; let (graph, handler) = setup(specifier).await;
let (stats, diagnostics, maybe_ignored_options) = graph let result_info = graph
.check(CheckOptions { .check(CheckOptions {
debug: false, debug: false,
emit: false, emit: false,
@ -1726,9 +1921,9 @@ pub mod tests {
reload: false, reload: false,
}) })
.expect("should have checked"); .expect("should have checked");
assert!(maybe_ignored_options.is_none()); assert!(result_info.maybe_ignored_options.is_none());
assert_eq!(stats.0.len(), 12); assert_eq!(result_info.stats.0.len(), 12);
assert!(diagnostics.is_empty()); assert!(result_info.diagnostics.is_empty());
let h = handler.borrow(); let h = handler.borrow();
assert_eq!(h.cache_calls.len(), 0); assert_eq!(h.cache_calls.len(), 0);
assert_eq!(h.tsbuildinfo_calls.len(), 1); assert_eq!(h.tsbuildinfo_calls.len(), 1);
@ -1740,7 +1935,7 @@ pub mod tests {
ModuleSpecifier::resolve_url_or_path("file:///tests/checkwithconfig.ts") ModuleSpecifier::resolve_url_or_path("file:///tests/checkwithconfig.ts")
.expect("could not resolve module"); .expect("could not resolve module");
let (graph, handler) = setup(specifier.clone()).await; let (graph, handler) = setup(specifier.clone()).await;
let (_, diagnostics, maybe_ignored_options) = graph let result_info = graph
.check(CheckOptions { .check(CheckOptions {
debug: false, debug: false,
emit: true, emit: true,
@ -1751,8 +1946,8 @@ pub mod tests {
reload: true, reload: true,
}) })
.expect("should have checked"); .expect("should have checked");
assert!(maybe_ignored_options.is_none()); assert!(result_info.maybe_ignored_options.is_none());
assert!(diagnostics.is_empty()); assert!(result_info.diagnostics.is_empty());
let h = handler.borrow(); let h = handler.borrow();
assert_eq!(h.version_calls.len(), 2); assert_eq!(h.version_calls.len(), 2);
let ver0 = h.version_calls[0].1.clone(); 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's do it all over again to ensure that the versions are determinstic
let (graph, handler) = setup(specifier).await; let (graph, handler) = setup(specifier).await;
let (_, diagnostics, maybe_ignored_options) = graph let result_info = graph
.check(CheckOptions { .check(CheckOptions {
debug: false, debug: false,
emit: true, emit: true,
@ -1771,14 +1966,89 @@ pub mod tests {
reload: true, reload: true,
}) })
.expect("should have checked"); .expect("should have checked");
assert!(maybe_ignored_options.is_none()); assert!(result_info.maybe_ignored_options.is_none());
assert!(diagnostics.is_empty()); assert!(result_info.diagnostics.is_empty());
let h = handler.borrow(); let h = handler.borrow();
assert_eq!(h.version_calls.len(), 2); assert_eq!(h.version_calls.len(), 2);
assert!(h.version_calls[0].1 == ver0 || h.version_calls[0].1 == ver1); 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); 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] #[tokio::test]
async fn test_graph_info() { async fn test_graph_info() {
let specifier = let specifier =

View file

@ -1,9 +1,9 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::import_map::ImportMap; use crate::import_map::ImportMap;
use crate::module_graph2::TypeLib;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::program_state::ProgramState; use crate::program_state::ProgramState;
use crate::tsc::TargetLib;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future::FutureExt; use deno_core::futures::future::FutureExt;
use deno_core::futures::Future; use deno_core::futures::Future;
@ -21,7 +21,7 @@ pub struct CliModuleLoader {
/// When flags contains a `.import_map_path` option, the content of the /// When flags contains a `.import_map_path` option, the content of the
/// import map file will be resolved and set. /// import map file will be resolved and set.
pub import_map: Option<ImportMap>, pub import_map: Option<ImportMap>,
pub target_lib: TargetLib, pub lib: TypeLib,
pub is_main: bool, pub is_main: bool,
} }
@ -29,7 +29,7 @@ impl CliModuleLoader {
pub fn new(maybe_import_map: Option<ImportMap>) -> Rc<Self> { pub fn new(maybe_import_map: Option<ImportMap>) -> Rc<Self> {
Rc::new(CliModuleLoader { Rc::new(CliModuleLoader {
import_map: maybe_import_map, import_map: maybe_import_map,
target_lib: TargetLib::Main, lib: TypeLib::DenoWindow,
is_main: true, is_main: true,
}) })
} }
@ -37,7 +37,7 @@ impl CliModuleLoader {
pub fn new_for_worker() -> Rc<Self> { pub fn new_for_worker() -> Rc<Self> {
Rc::new(CliModuleLoader { Rc::new(CliModuleLoader {
import_map: None, import_map: None,
target_lib: TargetLib::Worker, lib: TypeLib::DenoWorker,
is_main: false, is_main: false,
}) })
} }
@ -117,13 +117,21 @@ impl ModuleLoader for CliModuleLoader {
is_dynamic: bool, is_dynamic: bool,
) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> { ) -> Pin<Box<dyn Future<Output = Result<(), AnyError>>>> {
let specifier = specifier.clone(); let specifier = specifier.clone();
let target_lib = self.target_lib.clone();
let maybe_import_map = self.import_map.clone(); let maybe_import_map = self.import_map.clone();
let state = op_state.borrow(); let state = op_state.borrow();
// The permissions that should be applied to any dynamically imported module // The permissions that should be applied to any dynamically imported module
let dynamic_permissions = state.borrow::<Permissions>().clone(); let dynamic_permissions = state.borrow::<Permissions>().clone();
let program_state = state.borrow::<Arc<ProgramState>>().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); drop(state);
// TODO(bartlomieju): `prepare_module_load` should take `load_id` param // TODO(bartlomieju): `prepare_module_load` should take `load_id` param
@ -131,7 +139,7 @@ impl ModuleLoader for CliModuleLoader {
program_state program_state
.prepare_module_load( .prepare_module_load(
specifier, specifier,
target_lib, lib,
dynamic_permissions, dynamic_permissions,
is_dynamic, is_dynamic,
maybe_import_map, maybe_import_map,

View file

@ -17,8 +17,6 @@ pub fn get_asset(name: &str) -> Option<&'static str> {
}; };
} }
match name { 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\");"), "bootstrap.ts" => Some("console.log(\"hello deno\");"),
"typescript.d.ts" => inc!("typescript.d.ts"), "typescript.d.ts" => inc!("typescript.d.ts"),
"lib.dom.d.ts" => inc!("lib.dom.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 /// Warning: Returns a non-JSON op dispatcher. Must be manually attached to
/// JsRuntime. /// 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( pub fn op_fetch_asset(
custom_assets: HashMap<String, PathBuf>, custom_assets: HashMap<String, PathBuf>,
) -> impl Fn(Rc<RefCell<OpState>>, BufVec) -> Op { ) -> impl Fn(Rc<RefCell<OpState>>, BufVec) -> Op {

View file

@ -3,17 +3,23 @@
use crate::ast; use crate::ast;
use crate::colors; use crate::colors;
use crate::media_type::MediaType; 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::permissions::Permissions;
use crate::tsc::runtime_bundle; use crate::specifier_handler::FetchHandler;
use crate::tsc::runtime_compile; use crate::specifier_handler::MemoryHandler;
use crate::specifier_handler::SpecifierHandler;
use crate::tsc_config; use crate::tsc_config;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::FutureExt; use deno_core::error::Context;
use deno_core::serde::Serialize; use deno_core::serde::Serialize;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::serde_json::json; use deno_core::serde_json::json;
use deno_core::serde_json::Value; use deno_core::serde_json::Value;
use deno_core::BufVec; use deno_core::BufVec;
use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
use serde::Deserialize; use serde::Deserialize;
use std::cell::RefCell; use std::cell::RefCell;
@ -39,35 +45,53 @@ async fn op_compile(
args: Value, args: Value,
_data: BufVec, _data: BufVec,
) -> Result<Value, AnyError> { ) -> Result<Value, AnyError> {
super::check_unstable2(&state, "Deno.compile");
let args: CompileArgs = serde_json::from_value(args)?; let args: CompileArgs = serde_json::from_value(args)?;
let cli_state = super::global_state2(&state); if args.bundle {
let program_state = cli_state.clone(); super::check_unstable2(&state, "Deno.bundle");
let permissions = { } else {
super::check_unstable2(&state, "Deno.compile");
}
let program_state = super::global_state2(&state);
let runtime_permissions = {
let state = state.borrow(); let state = state.borrow();
state.borrow::<Permissions>().clone() state.borrow::<Permissions>().clone()
}; };
let fut = if args.bundle { let handler: Rc<RefCell<dyn SpecifierHandler>> =
runtime_bundle( if let Some(sources) = args.sources {
&program_state, Rc::new(RefCell::new(MemoryHandler::new(sources)))
permissions,
&args.root_name,
&args.sources,
&args.options,
)
.boxed_local()
} else { } else {
runtime_compile( Rc::new(RefCell::new(FetchHandler::new(
&program_state, &program_state,
permissions, runtime_permissions,
&args.root_name, )?))
&args.sources,
&args.options,
)
.boxed_local()
}; };
let result = fut.await?; let mut builder = GraphBuilder2::new(handler, None, None);
Ok(result) 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 {
BundleType::None
};
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)] #[derive(Deserialize, Debug)]

View file

@ -15,9 +15,6 @@ use crate::module_graph2::TypeLib;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::source_maps::SourceMapGetter; use crate::source_maps::SourceMapGetter;
use crate::specifier_handler::FetchHandler; 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::generic_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
@ -37,6 +34,12 @@ pub fn exit_unstable(api_name: &str) {
std::process::exit(70); 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. /// This structure represents state of single "deno" program.
/// ///
/// It is shared by all created workers (thus V8 isolates). /// It is shared by all created workers (thus V8 isolates).
@ -47,7 +50,6 @@ pub struct ProgramState {
pub permissions: Permissions, pub permissions: Permissions,
pub dir: deno_dir::DenoDir, pub dir: deno_dir::DenoDir,
pub file_fetcher: SourceFileFetcher, pub file_fetcher: SourceFileFetcher,
pub ts_compiler: TsCompiler,
pub lockfile: Option<Arc<Mutex<Lockfile>>>, pub lockfile: Option<Arc<Mutex<Lockfile>>>,
pub maybe_import_map: Option<ImportMap>, pub maybe_import_map: Option<ImportMap>,
pub maybe_inspector_server: Option<Arc<InspectorServer>>, pub maybe_inspector_server: Option<Arc<InspectorServer>>,
@ -70,12 +72,6 @@ impl ProgramState {
ca_file.as_deref(), 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 = if let Some(filename) = &flags.lock {
let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?; let lockfile = Lockfile::new(filename.clone(), flags.lock_write)?;
Some(Arc::new(Mutex::new(lockfile))) Some(Arc::new(Mutex::new(lockfile)))
@ -105,7 +101,6 @@ impl ProgramState {
permissions: Permissions::from_flags(&flags), permissions: Permissions::from_flags(&flags),
flags, flags,
file_fetcher, file_fetcher,
ts_compiler,
lockfile, lockfile,
maybe_import_map, maybe_import_map,
maybe_inspector_server, maybe_inspector_server,
@ -120,7 +115,7 @@ impl ProgramState {
pub async fn prepare_module_load( pub async fn prepare_module_load(
self: &Arc<Self>, self: &Arc<Self>,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
target_lib: TargetLib, lib: TypeLib,
runtime_permissions: Permissions, runtime_permissions: Permissions,
is_dynamic: bool, is_dynamic: bool,
maybe_import_map: Option<ImportMap>, maybe_import_map: Option<ImportMap>,
@ -129,7 +124,7 @@ impl ProgramState {
// Workers are subject to the current runtime permissions. We do the // Workers are subject to the current runtime permissions. We do the
// permission check here early to avoid "wasting" time building a module // permission check here early to avoid "wasting" time building a module
// graph for a module that cannot be loaded. // 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)?; runtime_permissions.check_specifier(&specifier)?;
} }
let handler = let handler =
@ -153,24 +148,7 @@ impl ProgramState {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
} else { } else {
let lib = match target_lib { let result_info = graph.check(CheckOptions {
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, debug,
emit: true, emit: true,
lib, lib,
@ -178,12 +156,12 @@ impl ProgramState {
reload: self.flags.reload, reload: self.flags.reload,
})?; })?;
debug!("{}", stats); debug!("{}", result_info.stats);
if let Some(ignored_options) = maybe_ignored_options { if let Some(ignored_options) = result_info.maybe_ignored_options {
eprintln!("{}", ignored_options); eprintln!("{}", ignored_options);
} }
if !diagnostics.is_empty() { if !result_info.diagnostics.is_empty() {
return Err(generic_error(diagnostics.to_string())); return Err(generic_error(result_info.diagnostics.to_string()));
} }
}; };

View file

@ -52,19 +52,14 @@
sources: !!sources, sources: !!sources,
options, options,
}); });
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
const result = await opCompile(payload); const result = await opCompile(payload);
util.assert(result.emitMap); util.assert(result.emittedFiles);
const maybeDiagnostics = result.diagnostics.length === 0 const maybeDiagnostics = result.diagnostics.length === 0
? undefined ? undefined
: result.diagnostics; : result.diagnostics;
const emitMap = {}; return [maybeDiagnostics, result.emittedFiles];
for (const [key, emittedSource] of Object.entries(result.emitMap)) {
emitMap[key] = emittedSource.contents;
}
return [maybeDiagnostics, emitMap];
} }
// TODO(bartlomieju): change return type to interface? // TODO(bartlomieju): change return type to interface?
@ -84,12 +79,14 @@
sources: !!sources, sources: !!sources,
options, options,
}); });
/** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
const result = await opCompile(payload); 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 const maybeDiagnostics = result.diagnostics.length === 0
? undefined ? undefined
: result.diagnostics; : result.diagnostics;
return [maybeDiagnostics, result.output]; return [maybeDiagnostics, output];
} }
window.__bootstrap.compilerApi = { window.__bootstrap.compilerApi = {

View file

@ -8,7 +8,9 @@ use crate::media_type::MediaType;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::program_state::ProgramState; use crate::program_state::ProgramState;
use deno_core::error::custom_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::futures::future;
use deno_core::futures::Future; use deno_core::futures::Future;
use deno_core::futures::FutureExt; use deno_core::futures::FutureExt;
use deno_core::serde_json; use deno_core::serde_json;
@ -61,7 +63,6 @@ pub struct CachedModule {
pub specifier: ModuleSpecifier, pub specifier: ModuleSpecifier,
} }
#[cfg(test)]
impl Default for CachedModule { impl Default for CachedModule {
fn default() -> Self { fn default() -> Self {
let specifier = ModuleSpecifier::resolve_url("file:///example.js").unwrap(); 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)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::http_cache::HttpCache; use crate::http_cache::HttpCache;
use tempfile::TempDir; 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) { fn setup() -> (TempDir, FetchHandler) {
let temp_dir = TempDir::new().expect("could not setup"); let temp_dir = TempDir::new().expect("could not setup");
let deno_dir = DenoDir::new(Some(temp_dir.path().to_path_buf())) 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(); file_fetcher.fetch(specifier, None, false).await.unwrap();
assert_eq!(cached_module.is_remote, false); 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);
}
} }

View file

@ -14,12 +14,11 @@ Deno.test({
}); });
assert(diagnostics == null); assert(diagnostics == null);
assert(actual); assert(actual);
assertEquals(Object.keys(actual), [ const keys = Object.keys(actual).sort();
"/bar.js.map", assert(keys[0].endsWith("/bar.ts.js"));
"/bar.js", assert(keys[1].endsWith("/bar.ts.js.map"));
"/foo.js.map", assert(keys[2].endsWith("/foo.ts.js"));
"/foo.js", assert(keys[3].endsWith("/foo.ts.js.map"));
]);
}, },
}); });
@ -29,10 +28,10 @@ Deno.test({
const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts"); const [diagnostics, actual] = await Deno.compile("./subdir/mod1.ts");
assert(diagnostics == null); assert(diagnostics == null);
assert(actual); assert(actual);
const keys = Object.keys(actual); const keys = Object.keys(actual).sort();
assertEquals(keys.length, 6); assertEquals(keys.length, 6);
assert(keys[0].endsWith("print_hello.js.map")); assert(keys[0].endsWith("cli/tests/subdir/mod1.ts.js"));
assert(keys[1].endsWith("print_hello.js")); assert(keys[1].endsWith("cli/tests/subdir/mod1.ts.js.map"));
}, },
}); });
@ -51,8 +50,11 @@ Deno.test({
); );
assert(diagnostics == null); assert(diagnostics == null);
assert(actual); assert(actual);
assertEquals(Object.keys(actual), ["/foo.js"]); const keys = Object.keys(actual);
assert(actual["/foo.js"].startsWith("define(")); 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", name: "Deno.compile() - pass lib in compiler options",
async fn() { async fn() {
const [diagnostics, actual] = await Deno.compile( 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);`, console.log(Deno.args);`,
}, },
{ {
@ -71,45 +73,37 @@ Deno.test({
); );
assert(diagnostics == null); assert(diagnostics == null);
assert(actual); 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({ // TODO(@kitsonk) figure the "right way" to restore support for types
name: "Deno.compile() - pass outDir in compiler options", // Deno.test({
async fn() { // name: "Deno.compile() - properly handles .d.ts files",
const [diagnostics, actual] = await Deno.compile( // async fn() {
"src/foo.ts", // const [diagnostics, actual] = await Deno.compile(
{ // "/foo.ts",
"src/foo.ts": "console.log('Hello world')", // {
}, // "/foo.ts": `console.log(Foo.bar);`,
{ // "/foo_types.d.ts": `declare namespace Foo {
outDir: "./lib", // const bar: string;
}, // }`,
); // },
assert(diagnostics == null); // {
assert(actual); // types: ["/foo_types.d.ts"],
assertEquals(Object.keys(actual), ["lib/foo.js.map", "lib/foo.js"]); // },
}, // );
}); // assert(diagnostics == null);
// assert(actual);
Deno.test({ // assertEquals(
name: "Deno.compile() - properly handles .d.ts files", // Object.keys(actual).sort(),
async fn() { // ["file:///foo.ts.js", "file:///file.ts.js.map"],
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"]);
},
});
Deno.test({ Deno.test({
name: "Deno.transpileOnly()", name: "Deno.transpileOnly()",
@ -150,8 +144,7 @@ Deno.test({
"/bar.ts": `export const bar = "bar";\n`, "/bar.ts": `export const bar = "bar";\n`,
}); });
assert(diagnostics == null); assert(diagnostics == null);
assert(actual.includes(`__instantiate("foo", false)`)); assert(actual.includes(`const bar = "bar"`));
assert(actual.includes(`__exp["bar"]`));
}, },
}); });
@ -160,26 +153,7 @@ Deno.test({
async fn() { async fn() {
const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts"); const [diagnostics, actual] = await Deno.bundle("./subdir/mod1.ts");
assert(diagnostics == null); assert(diagnostics == null);
assert(actual.includes(`__instantiate("mod1", false)`)); assert(actual.length);
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`));
}, },
}); });
@ -191,22 +165,7 @@ Deno.test({
"/bar.js": `export const bar = "bar";\n`, "/bar.js": `export const bar = "bar";\n`,
}); });
assert(diagnostics == null); assert(diagnostics == null);
assert(actual.includes(`System.register("bar",`)); assert(actual.includes(`const bar = "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 = `));
}, },
}); });
@ -226,8 +185,8 @@ Deno.test({
name: "Deno.compile() - SWC diagnostics", name: "Deno.compile() - SWC diagnostics",
async fn() { async fn() {
await assertThrowsAsync(async () => { await assertThrowsAsync(async () => {
await Deno.compile("main.js", { await Deno.compile("/main.js", {
"main.js": ` "/main.js": `
export class Foo { export class Foo {
constructor() { constructor() {
console.log("foo"); console.log("foo");

View file

@ -1 +0,0 @@
Deno.compile("main.js", { "main.js": "console.log(foo);" });

View file

@ -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]

View file

@ -3001,12 +3001,6 @@ itest!(deno_doc_import_map {
output: "doc/use_import_map.out", 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 { itest!(import_file_with_colon {
args: "run --quiet --reload import_file_with_colon.ts", args: "run --quiet --reload import_file_with_colon.ts",
output: "import_file_with_colon.ts.out", output: "import_file_with_colon.ts.out",

View file

@ -1,7 +1,7 @@
const [errors, program] = await Deno.compile( const [errors, program] = await Deno.compile(
"main.ts", "/main.ts",
{ {
"main.ts": "/main.ts":
`/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`, `/// <reference lib="dom" />\n\ndocument.getElementById("foo");\nDeno.args;`,
}, },
{ {
@ -11,4 +11,4 @@ const [errors, program] = await Deno.compile(
); );
console.log(errors); console.log(errors);
console.log(Object.keys(program)); console.log(Object.keys(program).sort());

View file

@ -1,2 +1,2 @@
undefined undefined
[ "main.js.map", "main.js" ] [ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]

View file

@ -1,7 +1,7 @@
const [errors, program] = await Deno.compile( const [errors, program] = await Deno.compile(
"main.ts", "/main.ts",
{ {
"main.ts": `document.getElementById("foo");`, "/main.ts": `document.getElementById("foo");`,
}, },
{ {
lib: ["dom", "esnext"], lib: ["dom", "esnext"],
@ -9,4 +9,4 @@ const [errors, program] = await Deno.compile(
); );
console.log(errors); console.log(errors);
console.log(Object.keys(program)); console.log(Object.keys(program).sort());

View file

@ -1,2 +1,2 @@
undefined undefined
[ "main.js.map", "main.js" ] [ "file:///[WILDCARD]main.ts.js", "file:///[WILDCARD]main.ts.js.map" ]

1005
cli/tsc.rs

File diff suppressed because it is too large Load diff

View file

@ -118,9 +118,6 @@ delete Object.prototype.__proto__;
return core.decode(sourceCodeBytes); 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 // Using incremental compile APIs requires that all
// paths must be either relative or absolute. Since // paths must be either relative or absolute. Since
// analysis in Rust operates on fully resolved URLs, // analysis in Rust operates on fully resolved URLs,
@ -218,18 +215,6 @@ delete Object.prototype.__proto__;
*/ */
const RESOLVED_SPECIFIER_CACHE = new Map(); 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 { class SourceFile {
constructor(json) { constructor(json) {
this.processed = false; this.processed = false;
@ -541,95 +526,6 @@ delete Object.prototype.__proto__;
host, 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 = [ const IGNORED_DIAGNOSTICS = [
// TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is // TS2306: File 'file:///Users/rld/src/deno/cli/tests/subdir/amd_like.js' is
// not a module. // not a module.
@ -674,7 +570,6 @@ delete Object.prototype.__proto__;
function performanceStart() { function performanceStart() {
stats.length = 0; stats.length = 0;
// TODO(kitsonk) replace with performance.mark() when landed
statsStart = new Date(); statsStart = new Date();
ts.performance.enable(); ts.performance.enable();
} }
@ -716,317 +611,6 @@ delete Object.prototype.__proto__;
return stats; 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 * @typedef {object} Request
* @property {Record<string, any>} config * @property {Record<string, any>} config
@ -1094,6 +678,4 @@ delete Object.prototype.__proto__;
globalThis.startup = startup; globalThis.startup = startup;
globalThis.exec = exec; globalThis.exec = exec;
// TODO(@kitsonk) remove when converted from legacy tsc
globalThis.tsCompilerOnMessage = tsCompilerOnMessage;
})(this); })(this);

View file

@ -52,37 +52,49 @@ impl fmt::Display for IgnoredCompilerOptions {
/// A static slice of all the compiler options that should be ignored that /// 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 /// either have no effect on the compilation or would cause the emit to not work
/// in Deno. /// in Deno.
const IGNORED_COMPILER_OPTIONS: [&str; 10] = [ const IGNORED_COMPILER_OPTIONS: &[&str] = &[
"allowSyntheticDefaultImports", "allowSyntheticDefaultImports",
"allowUmdGlobalAccess",
"baseUrl",
"declaration",
"declarationMap",
"downlevelIteration",
"esModuleInterop", "esModuleInterop",
"emitDeclarationOnly",
"importHelpers",
"inlineSourceMap", "inlineSourceMap",
"inlineSources", "inlineSources",
// TODO(nayeemrmn): Add "isolatedModules" here for 1.6.0. // TODO(nayeemrmn): Add "isolatedModules" here for 1.6.0.
"module", "module",
"noEmitHelpers",
"noLib", "noLib",
"noResolve",
"outDir",
"paths",
"preserveConstEnums", "preserveConstEnums",
"reactNamespace", "reactNamespace",
"rootDir",
"rootDirs",
"skipLibCheck",
"sourceMap", "sourceMap",
"sourceRoot",
"target", "target",
"types",
"useDefineForClassFields",
]; ];
const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [ const IGNORED_RUNTIME_COMPILER_OPTIONS: &[&str] = &[
"allowUmdGlobalAccess",
"assumeChangesOnlyAffectDirectDependencies", "assumeChangesOnlyAffectDirectDependencies",
"baseUrl",
"build", "build",
"charset",
"composite", "composite",
"declaration",
"declarationMap",
"diagnostics", "diagnostics",
"downlevelIteration", "disableSizeLimit",
"emitBOM", "emitBOM",
"emitDeclarationOnly",
"extendedDiagnostics", "extendedDiagnostics",
"forceConsistentCasingInFileNames", "forceConsistentCasingInFileNames",
"generateCpuProfile", "generateCpuProfile",
"help", "help",
"importHelpers",
"incremental", "incremental",
"init", "init",
"listEmittedFiles", "listEmittedFiles",
@ -92,29 +104,21 @@ const IGNORED_RUNTIME_COMPILER_OPTIONS: [&str; 50] = [
"moduleResolution", "moduleResolution",
"newLine", "newLine",
"noEmit", "noEmit",
"noEmitHelpers",
"noEmitOnError", "noEmitOnError",
"noResolve",
"out", "out",
"outDir", "outDir",
"outFile", "outFile",
"paths",
"preserveSymlinks", "preserveSymlinks",
"preserveWatchOutput", "preserveWatchOutput",
"pretty", "pretty",
"project",
"resolveJsonModule", "resolveJsonModule",
"rootDir",
"rootDirs",
"showConfig", "showConfig",
"skipDefaultLibCheck", "skipDefaultLibCheck",
"skipLibCheck",
"sourceRoot",
"stripInternal", "stripInternal",
"traceResolution", "traceResolution",
"tsBuildInfoFile", "tsBuildInfoFile",
"types",
"typeRoots", "typeRoots",
"useDefineForClassFields",
"version", "version",
"watch", "watch",
]; ];
@ -170,12 +174,6 @@ struct TSConfigJson {
type_acquisition: Option<Value>, 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( fn parse_compiler_options(
compiler_options: &HashMap<String, Value>, compiler_options: &HashMap<String, Value>,
maybe_path: Option<PathBuf>, 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] #[test]
fn test_tsconfig_as_bytes() { fn test_tsconfig_as_bytes() {
let mut tsconfig1 = TsConfig::new(json!({ let mut tsconfig1 = TsConfig::new(json!({