// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. extern crate deno; extern crate serde; extern crate serde_json; mod ops; use deno::js_check; pub use deno::v8_set_flags; use deno::ErrBox; use deno::Isolate; use deno::ModuleSpecifier; use deno::StartupData; pub use ops::EmitResult; use ops::WrittenFile; use std::collections::HashMap; use std::fs; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; static TYPESCRIPT_CODE: &str = include_str!("../third_party/node_modules/typescript/lib/typescript.js"); static COMPILER_CODE: &str = include_str!("compiler_main.js"); static AMD_RUNTIME_CODE: &str = include_str!("amd_runtime.js"); #[derive(Debug)] pub struct TSState { bundle: bool, exit_code: i32, emit_result: Option, /// A list of files emitted by typescript. WrittenFile is tuple of the form /// (url, corresponding_module, source_code) written_files: Vec, } impl TSState { fn main_module_name(&self) -> String { // Assuming that TypeScript has emitted the main file last. self.written_files.last().unwrap().module_name.clone() } } pub struct TSIsolate { isolate: Isolate, state: Arc>, } impl TSIsolate { fn new(bundle: bool) -> TSIsolate { let mut isolate = Isolate::new(StartupData::None, false); js_check(isolate.execute("assets/typescript.js", TYPESCRIPT_CODE)); js_check(isolate.execute("compiler_main.js", COMPILER_CODE)); let state = Arc::new(Mutex::new(TSState { bundle, exit_code: 0, emit_result: None, written_files: Vec::new(), })); let state_ = state.clone(); isolate.set_dispatch(move |op_id, control_buf, zero_copy_buf| { assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler. let mut s = state_.lock().unwrap(); ops::dispatch_op(&mut s, op_id, control_buf) }); TSIsolate { isolate, state } } // TODO(ry) Instead of Result>, ErrBox>, return something // like Result. I think it would be nicer if this function // consumes TSIsolate. /// Compiles each module to ESM. Doesn't write any files to disk. /// Passes all output via state. fn compile( mut self, config_json: &serde_json::Value, root_names: Vec, ) -> Result>, ErrBox> { let root_names_json = serde_json::json!(root_names).to_string(); let source = &format!( "main({:?}, {}, {})", config_json.to_string(), root_names_json, preprocessor_replacements_json() ); self.isolate.execute("", source)?; Ok(self.state.clone()) } } pub fn compile_bundle( bundle: &Path, root_names: Vec, ) -> Result>, ErrBox> { let ts_isolate = TSIsolate::new(true); let config_json = serde_json::json!({ "compilerOptions": { "declaration": true, "lib": ["esnext"], "module": "amd", "target": "esnext", "listFiles": true, "listEmittedFiles": true, // "types" : ["typescript.d.ts"], "typeRoots" : ["$typeRoots$"], // Emit the source alongside the sourcemaps within a single file; // requires --inlineSourceMap or --sourceMap to be set. // "inlineSources": true, "sourceMap": true, "outFile": bundle, }, }); let mut root_names_str: Vec = root_names .iter() .map(|p| { if !p.exists() { panic!("File not found {}", p.display()); } let module_specifier = ModuleSpecifier::resolve_url_or_path(&p.to_string_lossy()).unwrap(); module_specifier.as_str().to_string() }) .collect(); root_names_str.push("$asset$/lib.deno_core.d.ts".to_string()); // TODO lift js_check to caller? let state = js_check(ts_isolate.compile(&config_json, root_names_str)); Ok(state) } #[allow(dead_code)] fn print_source_code(code: &str) { let mut i = 1; for line in code.lines() { println!("{:3} {}", i, line); i += 1; } } /// Create a V8 snapshot. pub fn mksnapshot_bundle( bundle: &Path, state: Arc>, ) -> Result<(), ErrBox> { let mut runtime_isolate = Isolate::new(StartupData::None, true); let source_code_vec = std::fs::read(bundle)?; let source_code = std::str::from_utf8(&source_code_vec)?; js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE)); js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code)); let main = state.lock().unwrap().main_module_name(); js_check(runtime_isolate.execute("anon", &format!("require('{}')", main))); write_snapshot(runtime_isolate, bundle)?; Ok(()) } /// Create a V8 snapshot. This differs from mksnapshot_bundle in that is also /// runs typescript.js pub fn mksnapshot_bundle_ts( bundle: &Path, state: Arc>, ) -> Result<(), ErrBox> { let mut runtime_isolate = Isolate::new(StartupData::None, true); let source_code_vec = std::fs::read(bundle)?; let source_code = std::str::from_utf8(&source_code_vec)?; js_check(runtime_isolate.execute("amd_runtime.js", AMD_RUNTIME_CODE)); js_check(runtime_isolate.execute("typescript.js", TYPESCRIPT_CODE)); js_check(runtime_isolate.execute(&bundle.to_string_lossy(), &source_code)); let main = state.lock().unwrap().main_module_name(); js_check(runtime_isolate.execute("anon", &format!("require('{}')", main))); write_snapshot(runtime_isolate, bundle)?; Ok(()) } fn write_snapshot( runtime_isolate: Isolate, bundle: &Path, ) -> Result<(), ErrBox> { println!("creating snapshot..."); let snapshot = runtime_isolate.snapshot()?; let snapshot_slice = unsafe { std::slice::from_raw_parts(snapshot.data_ptr, snapshot.data_len) }; println!("snapshot bytes {}", snapshot_slice.len()); let snapshot_path = bundle.with_extension("bin"); fs::write(&snapshot_path, snapshot_slice)?; println!("snapshot path {} ", snapshot_path.display()); Ok(()) } macro_rules! inc { ($e:expr) => { Some(include_str!(concat!( "../third_party/node_modules/typescript/lib/", $e ))) }; } /// Same as get_asset() but returns NotFound intead of None. pub fn get_asset2(name: &str) -> Result<&'static str, ErrBox> { match get_asset(name) { Some(a) => Ok(a), None => Err( std::io::Error::new(std::io::ErrorKind::NotFound, "Asset not found") .into(), ), } } pub fn get_asset(name: &str) -> Option<&'static str> { match name { "lib.deno_core.d.ts" => Some(include_str!("lib.deno_core.d.ts")), "lib.esnext.d.ts" => inc!("lib.esnext.d.ts"), "lib.es2019.d.ts" => inc!("lib.es2019.d.ts"), "lib.es2018.d.ts" => inc!("lib.es2018.d.ts"), "lib.es2017.d.ts" => inc!("lib.es2017.d.ts"), "lib.es2016.d.ts" => inc!("lib.es2016.d.ts"), "lib.es5.d.ts" => inc!("lib.es5.d.ts"), "lib.es2015.d.ts" => inc!("lib.es2015.d.ts"), "lib.es2015.core.d.ts" => inc!("lib.es2015.core.d.ts"), "lib.es2015.collection.d.ts" => inc!("lib.es2015.collection.d.ts"), "lib.es2015.generator.d.ts" => inc!("lib.es2015.generator.d.ts"), "lib.es2015.iterable.d.ts" => inc!("lib.es2015.iterable.d.ts"), "lib.es2015.promise.d.ts" => inc!("lib.es2015.promise.d.ts"), "lib.es2015.symbol.d.ts" => inc!("lib.es2015.symbol.d.ts"), "lib.es2015.proxy.d.ts" => inc!("lib.es2015.proxy.d.ts"), "lib.es2015.symbol.wellknown.d.ts" => { inc!("lib.es2015.symbol.wellknown.d.ts") } "lib.es2015.reflect.d.ts" => inc!("lib.es2015.reflect.d.ts"), "lib.es2016.array.include.d.ts" => inc!("lib.es2016.array.include.d.ts"), "lib.es2017.object.d.ts" => inc!("lib.es2017.object.d.ts"), "lib.es2017.sharedmemory.d.ts" => inc!("lib.es2017.sharedmemory.d.ts"), "lib.es2017.string.d.ts" => inc!("lib.es2017.string.d.ts"), "lib.es2017.intl.d.ts" => inc!("lib.es2017.intl.d.ts"), "lib.es2017.typedarrays.d.ts" => inc!("lib.es2017.typedarrays.d.ts"), "lib.es2018.asynciterable.d.ts" => inc!("lib.es2018.asynciterable.d.ts"), "lib.es2018.promise.d.ts" => inc!("lib.es2018.promise.d.ts"), "lib.es2018.regexp.d.ts" => inc!("lib.es2018.regexp.d.ts"), "lib.es2018.intl.d.ts" => inc!("lib.es2018.intl.d.ts"), "lib.es2019.array.d.ts" => inc!("lib.es2019.array.d.ts"), "lib.es2019.object.d.ts" => inc!("lib.es2019.object.d.ts"), "lib.es2019.string.d.ts" => inc!("lib.es2019.string.d.ts"), "lib.es2019.symbol.d.ts" => inc!("lib.es2019.symbol.d.ts"), "lib.esnext.bigint.d.ts" => inc!("lib.esnext.bigint.d.ts"), "lib.esnext.intl.d.ts" => inc!("lib.esnext.intl.d.ts"), _ => None, } } /// Sets the --trace-serializer V8 flag for debugging snapshots. pub fn trace_serializer() { let dummy = "foo".to_string(); let r = deno::v8_set_flags(vec![dummy.clone(), "--trace-serializer".to_string()]); assert_eq!(r, vec![dummy]); } fn preprocessor_replacements_json() -> String { /// BUILD_OS and BUILD_ARCH match the values in Deno.build. See js/build.ts. #[cfg(target_os = "macos")] static BUILD_OS: &str = "mac"; #[cfg(target_os = "linux")] static BUILD_OS: &str = "linux"; #[cfg(target_os = "windows")] static BUILD_OS: &str = "win"; #[cfg(target_arch = "x86_64")] static BUILD_ARCH: &str = "x64"; let mut replacements = HashMap::new(); replacements.insert("DENO_REPLACE_OS", BUILD_OS); replacements.insert("DENO_REPLACE_ARCH", BUILD_ARCH); serde_json::json!(replacements).to_string() }