summaryrefslogtreecommitdiff
path: root/deno_typescript/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'deno_typescript/lib.rs')
-rw-r--r--deno_typescript/lib.rs288
1 files changed, 288 insertions, 0 deletions
diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs
new file mode 100644
index 000000000..cf4b39d09
--- /dev/null
+++ b/deno_typescript/lib.rs
@@ -0,0 +1,288 @@
+// 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<EmitResult>,
+ /// A list of files emitted by typescript. WrittenFile is tuple of the form
+ /// (url, corresponding_module, source_code)
+ written_files: Vec<WrittenFile>,
+}
+
+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<Mutex<TSState>>,
+}
+
+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<Arc<Mutex<TSState>>, ErrBox>, return something
+ // like Result<TSState, ErrBox>. 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<String>,
+ ) -> Result<Arc<Mutex<TSState>>, 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("<anon>", source)?;
+ Ok(self.state.clone())
+ }
+}
+
+pub fn compile_bundle(
+ bundle: &Path,
+ root_names: Vec<PathBuf>,
+) -> Result<Arc<Mutex<TSState>>, 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<String> = 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<Mutex<TSState>>,
+) -> 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<Mutex<TSState>>,
+) -> 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()
+}