summaryrefslogtreecommitdiff
path: root/cli/tsc.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tsc.rs')
-rw-r--r--cli/tsc.rs442
1 files changed, 255 insertions, 187 deletions
diff --git a/cli/tsc.rs b/cli/tsc.rs
index 664721bc1..2eb8a35a0 100644
--- a/cli/tsc.rs
+++ b/cli/tsc.rs
@@ -5,10 +5,9 @@ use crate::diagnostics::DiagnosticItem;
use crate::disk_cache::DiskCache;
use crate::file_fetcher::SourceFile;
use crate::file_fetcher::SourceFileFetcher;
-use crate::fmt;
-use crate::fs as deno_fs;
use crate::global_state::GlobalState;
use crate::import_map::ImportMap;
+use crate::module_graph::ModuleGraphFile;
use crate::module_graph::ModuleGraphLoader;
use crate::msg;
use crate::op_error::OpError;
@@ -16,7 +15,6 @@ use crate::ops;
use crate::permissions::Permissions;
use crate::source_maps::SourceMapGetter;
use crate::startup_data;
-use crate::state::exit_unstable;
use crate::state::State;
use crate::version;
use crate::web_worker::WebWorker;
@@ -50,73 +48,69 @@ use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::task::Poll;
-use std::time::Instant;
use url::Url;
-// TODO(bartlomieju): make static
-pub fn get_available_libs() -> Vec<String> {
- vec![
- "deno.ns".to_string(),
- "deno.window".to_string(),
- "deno.worker".to_string(),
- "deno.shared_globals".to_string(),
- "deno.unstable".to_string(),
- "dom".to_string(),
- "dom.iterable".to_string(),
- "es5".to_string(),
- "es6".to_string(),
- "esnext".to_string(),
- "es2020".to_string(),
- "es2020.full".to_string(),
- "es2019".to_string(),
- "es2019.full".to_string(),
- "es2018".to_string(),
- "es2018.full".to_string(),
- "es2017".to_string(),
- "es2017.full".to_string(),
- "es2016".to_string(),
- "es2016.full".to_string(),
- "es2015".to_string(),
- "es2015.collection".to_string(),
- "es2015.core".to_string(),
- "es2015.generator".to_string(),
- "es2015.iterable".to_string(),
- "es2015.promise".to_string(),
- "es2015.proxy".to_string(),
- "es2015.reflect".to_string(),
- "es2015.symbol".to_string(),
- "es2015.symbol.wellknown".to_string(),
- "es2016.array.include".to_string(),
- "es2017.intl".to_string(),
- "es2017.object".to_string(),
- "es2017.sharedmemory".to_string(),
- "es2017.string".to_string(),
- "es2017.typedarrays".to_string(),
- "es2018.asyncgenerator".to_string(),
- "es2018.asynciterable".to_string(),
- "es2018.intl".to_string(),
- "es2018.promise".to_string(),
- "es2018.regexp".to_string(),
- "es2019.array".to_string(),
- "es2019.object".to_string(),
- "es2019.string".to_string(),
- "es2019.symbol".to_string(),
- "es2020.bigint".to_string(),
- "es2020.promise".to_string(),
- "es2020.string".to_string(),
- "es2020.symbol.wellknown".to_string(),
- "esnext.array".to_string(),
- "esnext.asynciterable".to_string(),
- "esnext.bigint".to_string(),
- "esnext.intl".to_string(),
- "esnext.promise".to_string(),
- "esnext.string".to_string(),
- "esnext.symbol".to_string(),
- "scripthost".to_string(),
- "webworker".to_string(),
- "webworker.importscripts".to_string(),
- ]
-}
+pub const AVAILABLE_LIBS: &[&str] = &[
+ "deno.ns",
+ "deno.window",
+ "deno.worker",
+ "deno.shared_globals",
+ "deno.unstable",
+ "dom",
+ "dom.iterable",
+ "es5",
+ "es6",
+ "esnext",
+ "es2020",
+ "es2020.full",
+ "es2019",
+ "es2019.full",
+ "es2018",
+ "es2018.full",
+ "es2017",
+ "es2017.full",
+ "es2016",
+ "es2016.full",
+ "es2015",
+ "es2015.collection",
+ "es2015.core",
+ "es2015.generator",
+ "es2015.iterable",
+ "es2015.promise",
+ "es2015.proxy",
+ "es2015.reflect",
+ "es2015.symbol",
+ "es2015.symbol.wellknown",
+ "es2016.array.include",
+ "es2017.intl",
+ "es2017.object",
+ "es2017.sharedmemory",
+ "es2017.string",
+ "es2017.typedarrays",
+ "es2018.asyncgenerator",
+ "es2018.asynciterable",
+ "es2018.intl",
+ "es2018.promise",
+ "es2018.regexp",
+ "es2019.array",
+ "es2019.object",
+ "es2019.string",
+ "es2019.symbol",
+ "es2020.bigint",
+ "es2020.promise",
+ "es2020.string",
+ "es2020.symbol.wellknown",
+ "esnext.array",
+ "esnext.asynciterable",
+ "esnext.bigint",
+ "esnext.intl",
+ "esnext.promise",
+ "esnext.string",
+ "esnext.symbol",
+ "scripthost",
+ "webworker",
+ "webworker.importscripts",
+];
#[derive(Debug, Clone)]
pub struct CompiledModule {
@@ -160,6 +154,7 @@ impl Future for CompilerWorker {
}
}
+// TODO(bartlomieju): use JSONC parser from dprint instead of Regex
lazy_static! {
static ref CHECK_JS_RE: Regex =
Regex::new(r#""checkJs"\s*?:\s*?true"#).unwrap();
@@ -175,9 +170,14 @@ fn create_compiler_worker(
// like 'eval', 'repl'
let entry_point =
ModuleSpecifier::resolve_url_or_path("./__$deno$ts_compiler.ts").unwrap();
- let worker_state =
- State::new(global_state.clone(), Some(permissions), entry_point, true)
- .expect("Unable to create worker state");
+ let worker_state = State::new(
+ global_state.clone(),
+ Some(permissions),
+ entry_point,
+ None,
+ true,
+ )
+ .expect("Unable to create worker state");
// TODO(bartlomieju): this metric is never used anywhere
// Count how many times we start the compiler worker.
@@ -294,6 +294,28 @@ impl CompiledFileMetadata {
}
}
+/// Information associated with compilation of a "module graph",
+/// ie. entry point and all its dependencies.
+/// It's used to perform cache invalidation if content of any
+/// dependency changes.
+#[derive(Deserialize, Serialize)]
+pub struct GraphFileMetadata {
+ pub deps: Vec<String>,
+ pub version_hash: String,
+}
+
+impl GraphFileMetadata {
+ pub fn from_json_string(
+ metadata_string: String,
+ ) -> Result<Self, serde_json::Error> {
+ serde_json::from_str::<Self>(&metadata_string)
+ }
+
+ pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
+ serde_json::to_string(self)
+ }
+}
+
/// Emit a SHA256 hash based on source code, deno version and TS config.
/// Used to check if a recompilation for source code is needed.
pub fn source_code_version_hash(
@@ -383,6 +405,7 @@ impl TsCompiler {
})))
}
+ // TODO(bartlomieju): this method is no longer needed
/// Mark given module URL as compiled to avoid multiple compilations of same
/// module in single run.
fn mark_compiled(&self, url: &Url) {
@@ -390,11 +413,34 @@ impl TsCompiler {
c.insert(url.clone());
}
- /// Check if given module URL has already been compiled and can be fetched
- /// directly from disk.
- fn has_compiled(&self, url: &Url) -> bool {
- let c = self.compiled.lock().unwrap();
- c.contains(url)
+ /// Check if there is compiled source in cache that is valid
+ /// and can be used again.
+ // TODO(bartlomieju): there should be check that cached file actually exists
+ fn has_compiled_source(
+ &self,
+ file_fetcher: &SourceFileFetcher,
+ url: &Url,
+ ) -> bool {
+ let specifier = ModuleSpecifier::from(url.clone());
+ if let Some(source_file) = file_fetcher
+ .fetch_cached_source_file(&specifier, Permissions::allow_all())
+ {
+ if let Some(metadata) = self.get_metadata(&url) {
+ // 2. compare version hashes
+ // TODO: it would probably be good idea to make it method implemented on SourceFile
+ let version_hash_to_validate = source_code_version_hash(
+ &source_file.source_code,
+ version::DENO,
+ &self.config.hash,
+ );
+
+ if metadata.version_hash == version_hash_to_validate {
+ return true;
+ }
+ }
+ }
+
+ false
}
/// Asynchronously compile module and all it's dependencies.
@@ -406,64 +452,43 @@ impl TsCompiler {
///
/// If compilation is required then new V8 worker is spawned with fresh TS
/// compiler.
- pub async fn compile(
+ pub async fn compile_module_graph(
&self,
global_state: GlobalState,
source_file: &SourceFile,
target: TargetLib,
permissions: Permissions,
- is_dyn_import: bool,
- ) -> Result<CompiledModule, ErrBox> {
- if self.has_compiled(&source_file.url) {
- return self.get_compiled_module(&source_file.url);
- }
+ module_graph: HashMap<String, ModuleGraphFile>,
+ ) -> Result<(), ErrBox> {
+ let mut has_cached_version = false;
if self.use_disk_cache {
- // Try to load cached version:
- // 1. check if there's 'meta' file
- if let Some(metadata) = self.get_metadata(&source_file.url) {
- // 2. compare version hashes
- // TODO: it would probably be good idea to make it method implemented on SourceFile
- let version_hash_to_validate = source_code_version_hash(
- &source_file.source_code,
- version::DENO,
+ if let Some(metadata) = self.get_graph_metadata(&source_file.url) {
+ has_cached_version = true;
+
+ let version_hash = crate::checksum::gen(vec![
+ version::DENO.as_bytes(),
&self.config.hash,
- );
+ ]);
- if metadata.version_hash == version_hash_to_validate {
- debug!("load_cache metadata version hash match");
- if let Ok(compiled_module) =
- self.get_compiled_module(&source_file.url)
- {
- self.mark_compiled(&source_file.url);
- return Ok(compiled_module);
- }
+ has_cached_version &= metadata.version_hash == version_hash;
+ has_cached_version &= self
+ .has_compiled_source(&global_state.file_fetcher, &source_file.url);
+
+ for dep in metadata.deps {
+ let url = Url::parse(&dep).expect("Dep is not a valid url");
+ has_cached_version &=
+ self.has_compiled_source(&global_state.file_fetcher, &url);
}
}
}
- let source_file_ = source_file.clone();
+
+ if has_cached_version {
+ return Ok(());
+ }
+
let module_url = source_file.url.clone();
- let module_specifier = ModuleSpecifier::from(source_file.url.clone());
- let import_map: Option<ImportMap> =
- match global_state.flags.import_map_path.as_ref() {
- None => None,
- Some(file_path) => {
- if !global_state.flags.unstable {
- exit_unstable("--importmap")
- }
- Some(ImportMap::load(file_path)?)
- }
- };
- let mut module_graph_loader = ModuleGraphLoader::new(
- global_state.file_fetcher.clone(),
- import_map,
- permissions.clone(),
- is_dyn_import,
- true,
- );
- module_graph_loader.add_to_graph(&module_specifier).await?;
- let module_graph = module_graph_loader.get_graph();
let module_graph_json =
serde_json::to_value(module_graph).expect("Failed to serialize data");
let target = match target {
@@ -500,23 +525,17 @@ impl TsCompiler {
let req_msg = j.to_string().into_boxed_str().into_boxed_bytes();
- let ts_compiler = self.clone();
-
+ // TODO(bartlomieju): lift this call up - TSC shouldn't print anything
info!(
"{} {}",
colors::green("Compile".to_string()),
module_url.to_string()
);
- let start = Instant::now();
-
let msg =
execute_in_same_thread(global_state.clone(), permissions, req_msg)
.await?;
- let end = Instant::now();
- debug!("time spent in compiler thread {:#?}", end - start);
-
let json_str = std::str::from_utf8(&msg).unwrap();
let compile_response: CompileResponse = serde_json::from_str(json_str)?;
@@ -525,8 +544,69 @@ impl TsCompiler {
return Err(ErrBox::from(compile_response.diagnostics));
}
+ self.set_graph_metadata(
+ source_file.url.clone(),
+ &compile_response.emit_map,
+ )?;
self.cache_emitted_files(compile_response.emit_map)?;
- ts_compiler.get_compiled_module(&source_file_.url)
+ Ok(())
+ }
+
+ fn get_graph_metadata(&self, url: &Url) -> Option<GraphFileMetadata> {
+ // Try to load cached version:
+ // 1. check if there's 'meta' file
+ let cache_key = self
+ .disk_cache
+ .get_cache_filename_with_extension(url, "graph");
+ if let Ok(metadata_bytes) = self.disk_cache.get(&cache_key) {
+ if let Ok(metadata) = std::str::from_utf8(&metadata_bytes) {
+ if let Ok(read_metadata) =
+ GraphFileMetadata::from_json_string(metadata.to_string())
+ {
+ return Some(read_metadata);
+ }
+ }
+ }
+
+ None
+ }
+
+ fn set_graph_metadata(
+ &self,
+ url: Url,
+ emit_map: &HashMap<String, EmittedSource>,
+ ) -> std::io::Result<()> {
+ let version_hash =
+ crate::checksum::gen(vec![version::DENO.as_bytes(), &self.config.hash]);
+ let mut deps = vec![];
+
+ for (_emitted_name, source) in emit_map.iter() {
+ let specifier = ModuleSpecifier::resolve_url(&source.filename)
+ .expect("Should be a valid module specifier");
+
+ let source_file = self
+ .file_fetcher
+ .fetch_cached_source_file(&specifier, Permissions::allow_all())
+ .expect("Source file not found");
+
+ // NOTE: JavaScript files are only cached to disk if `checkJs`
+ // option in on
+ if source_file.media_type == msg::MediaType::JavaScript
+ && !self.compile_js
+ {
+ continue;
+ }
+
+ deps.push(specifier.to_string());
+ }
+
+ let graph_metadata = GraphFileMetadata { deps, version_hash };
+ let meta_key = self
+ .disk_cache
+ .get_cache_filename_with_extension(&url, "graph");
+ self
+ .disk_cache
+ .set(&meta_key, graph_metadata.to_json_string()?.as_bytes())
}
/// Get associated `CompiledFileMetadata` for given module if it exists.
@@ -557,10 +637,23 @@ impl TsCompiler {
let specifier = ModuleSpecifier::resolve_url(&source.filename)
.expect("Should be a valid module specifier");
+ let source_file = self
+ .file_fetcher
+ .fetch_cached_source_file(&specifier, Permissions::allow_all())
+ .expect("Source file not found");
+
+ // NOTE: JavaScript files are only cached to disk if `checkJs`
+ // option in on
+ if source_file.media_type == msg::MediaType::JavaScript
+ && !self.compile_js
+ {
+ continue;
+ }
+
if emitted_name.ends_with(".map") {
self.cache_source_map(&specifier, &source.contents)?;
} else if emitted_name.ends_with(".js") {
- self.cache_compiled_file(&specifier, &source.contents)?;
+ self.cache_compiled_file(&specifier, source_file, &source.contents)?;
} else {
panic!("Trying to cache unknown file type {}", emitted_name);
}
@@ -618,20 +711,9 @@ impl TsCompiler {
fn cache_compiled_file(
&self,
module_specifier: &ModuleSpecifier,
+ source_file: SourceFile,
contents: &str,
) -> std::io::Result<()> {
- let source_file = self
- .file_fetcher
- .fetch_cached_source_file(&module_specifier, Permissions::allow_all())
- .expect("Source file not found");
-
- // NOTE: JavaScript files are only cached to disk if `checkJs`
- // option in on
- if source_file.media_type == msg::MediaType::JavaScript && !self.compile_js
- {
- return Ok(());
- }
-
// By default TSC output source map url that is relative; we need
// to substitute it manually to correct file URL in DENO_DIR.
let mut content_lines = contents
@@ -664,10 +746,6 @@ impl TsCompiler {
.get_cache_filename_with_extension(module_specifier.as_url(), "js");
self.disk_cache.set(&js_key, contents.as_bytes())?;
self.mark_compiled(module_specifier.as_url());
- let source_file = self
- .file_fetcher
- .fetch_cached_source_file(&module_specifier, Permissions::allow_all())
- .expect("Source file not found");
let version_hash = source_code_version_hash(
&source_file.source_code,
@@ -720,18 +798,6 @@ impl TsCompiler {
module_specifier: &ModuleSpecifier,
contents: &str,
) -> std::io::Result<()> {
- let source_file = self
- .file_fetcher
- .fetch_cached_source_file(&module_specifier, Permissions::allow_all())
- .expect("Source file not found");
-
- // NOTE: JavaScript files are only cached to disk if `checkJs`
- // option in on
- if source_file.media_type == msg::MediaType::JavaScript && !self.compile_js
- {
- return Ok(());
- }
-
let js_key = self
.disk_cache
.get_cache_filename_with_extension(module_specifier.as_url(), "js");
@@ -854,14 +920,12 @@ pub async fn bundle(
compiler_config: CompilerConfig,
module_specifier: ModuleSpecifier,
maybe_import_map: Option<ImportMap>,
- out_file: Option<PathBuf>,
unstable: bool,
-) -> Result<(), ErrBox> {
+) -> Result<String, ErrBox> {
debug!(
"Invoking the compiler to bundle. module_name: {}",
module_specifier.to_string()
);
- eprintln!("Bundling {}", module_specifier.to_string());
let permissions = Permissions::allow_all();
let mut module_graph_loader = ModuleGraphLoader::new(
@@ -871,7 +935,9 @@ pub async fn bundle(
false,
true,
);
- module_graph_loader.add_to_graph(&module_specifier).await?;
+ module_graph_loader
+ .add_to_graph(&module_specifier, None)
+ .await?;
let module_graph = module_graph_loader.get_graph();
let module_graph_json =
serde_json::to_value(module_graph).expect("Failed to serialize data");
@@ -921,26 +987,7 @@ pub async fn bundle(
assert!(bundle_response.bundle_output.is_some());
let output = bundle_response.bundle_output.unwrap();
-
- // TODO(bartlomieju): the rest of this function should be handled
- // in `main.rs` - it has nothing to do with TypeScript...
- let output_string = fmt::format_text(&output)?;
-
- if let Some(out_file_) = out_file.as_ref() {
- eprintln!("Emitting bundle to {:?}", out_file_);
-
- let output_bytes = output_string.as_bytes();
- let output_len = output_bytes.len();
-
- deno_fs::write_file(out_file_, output_bytes, 0o666)?;
- // TODO(bartlomieju): do we really need to show this info? (it doesn't respect --quiet flag)
- // TODO(bartlomieju): add "humanFileSize" method
- eprintln!("{} bytes emitted.", output_len);
- } else {
- println!("{}", output_string);
- }
-
- Ok(())
+ Ok(output)
}
/// This function is used by `Deno.compile()` and `Deno.bundle()` APIs.
@@ -968,7 +1015,9 @@ pub async fn runtime_compile<S: BuildHasher>(
let module_specifier =
ModuleSpecifier::resolve_import(root_name, "<unknown>")?;
root_names.push(module_specifier.to_string());
- module_graph_loader.add_to_graph(&module_specifier).await?;
+ module_graph_loader
+ .add_to_graph(&module_specifier, None)
+ .await?;
}
// download all additional files from TSconfig and add them to root_names
@@ -983,7 +1032,9 @@ pub async fn runtime_compile<S: BuildHasher>(
.expect("type is not a string")
.to_string();
let type_specifier = ModuleSpecifier::resolve_url_or_path(&type_str)?;
- module_graph_loader.add_to_graph(&type_specifier).await?;
+ module_graph_loader
+ .add_to_graph(&type_specifier, None)
+ .await?;
root_names.push(type_specifier.to_string())
}
}
@@ -1078,18 +1129,36 @@ mod tests {
};
let mock_state =
GlobalState::mock(vec![String::from("deno"), String::from("hello.ts")]);
+
+ let mut module_graph_loader = ModuleGraphLoader::new(
+ mock_state.file_fetcher.clone(),
+ None,
+ Permissions::allow_all(),
+ false,
+ false,
+ );
+ module_graph_loader
+ .add_to_graph(&specifier, None)
+ .await
+ .expect("Failed to create graph");
+ let module_graph = module_graph_loader.get_graph();
+
let result = mock_state
.ts_compiler
- .compile(
+ .compile_module_graph(
mock_state.clone(),
&out,
TargetLib::Main,
Permissions::allow_all(),
- false,
+ module_graph,
)
.await;
assert!(result.is_ok());
- let source_code = result.unwrap().code;
+ let compiled_file = mock_state
+ .ts_compiler
+ .get_compiled_module(&out.url)
+ .unwrap();
+ let source_code = compiled_file.code;
assert!(source_code
.as_bytes()
.starts_with(b"\"use strict\";\nconsole.log(\"Hello World\");"));
@@ -1143,7 +1212,6 @@ mod tests {
CompilerConfig::load(None).unwrap(),
module_name,
None,
- None,
false,
)
.await;