summaryrefslogtreecommitdiff
path: root/deno_typescript
diff options
context:
space:
mode:
Diffstat (limited to 'deno_typescript')
-rw-r--r--deno_typescript/Cargo.toml24
-rw-r--r--deno_typescript/README.md8
-rw-r--r--deno_typescript/amd_runtime.js54
-rw-r--r--deno_typescript/compiler_main.js375
-rw-r--r--deno_typescript/lib.deno_core.d.ts66
-rw-r--r--deno_typescript/lib.rs299
-rw-r--r--deno_typescript/ops.rs137
m---------deno_typescript/typescript0
8 files changed, 963 insertions, 0 deletions
diff --git a/deno_typescript/Cargo.toml b/deno_typescript/Cargo.toml
new file mode 100644
index 000000000..584572fb4
--- /dev/null
+++ b/deno_typescript/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "deno_typescript"
+version = "0.20.0"
+license = "MIT"
+description = "To compile TypeScript to a snapshot during build.rs"
+repository = "https://github.com/denoland/deno"
+authors = ["the Deno authors"]
+edition = "2018"
+
+exclude = [
+ "typescript/tests/*",
+ "typescript/src/*",
+ "typescript/scripts/*",
+ "typescript/doc/*",
+ "typescript/lib/*/*.json",
+]
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+deno = { path = "../core", version = "0.20.0" }
+serde_json = "1.0.40"
+serde = { version = "1.0.100", features = ["derive"] }
diff --git a/deno_typescript/README.md b/deno_typescript/README.md
new file mode 100644
index 000000000..d51e88e72
--- /dev/null
+++ b/deno_typescript/README.md
@@ -0,0 +1,8 @@
+This crate provides utilities to compile typescript, bundle it up, and create a
+V8 snapshot, all during build. This allows users to startup fast.
+
+The cli_snapshots crate, neighboring this one uses deno_typescript at build
+time.
+
+This crate does not depend on Node, Python, nor any other external dependencies
+besides those listed as such in Cargo.toml.
diff --git a/deno_typescript/amd_runtime.js b/deno_typescript/amd_runtime.js
new file mode 100644
index 000000000..1c4f0007a
--- /dev/null
+++ b/deno_typescript/amd_runtime.js
@@ -0,0 +1,54 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// A very very basic AMD preamble to support the output of TypeScript outFile
+// bundles.
+
+/**
+ * @type {(name: string) => any}
+ */
+let require;
+
+/**
+ * @type {(name: string, deps: ReadonlyArray<string>, factory: (...deps: any[]) => void) => void}
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+let define;
+
+(function() {
+ /**
+ * @type {Map<string, { name: string, exports: any }>}
+ */
+ const modules = new Map();
+
+ /**
+ * @param {string} name
+ */
+ function createOrLoadModule(name) {
+ let m = modules.get(name);
+ if (!m) {
+ m = { name, exports: {} };
+ modules.set(name, m);
+ }
+ return m;
+ }
+
+ require = name => {
+ return createOrLoadModule(name).exports;
+ };
+
+ define = (name, deps, factory) => {
+ const currentModule = createOrLoadModule(name);
+ const localExports = currentModule.exports;
+ const args = deps.map(dep => {
+ if (dep === "require") {
+ return require;
+ } else if (dep === "exports") {
+ return localExports;
+ } else {
+ const depModule = createOrLoadModule(dep);
+ return depModule.exports;
+ }
+ });
+ factory(...args);
+ };
+})();
diff --git a/deno_typescript/compiler_main.js b/deno_typescript/compiler_main.js
new file mode 100644
index 000000000..fa155f12d
--- /dev/null
+++ b/deno_typescript/compiler_main.js
@@ -0,0 +1,375 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// Because we're bootstrapping the TypeScript compiler without dependencies on
+// Node, this is written in JavaScript, but leverages JSDoc that can be
+// understood by the TypeScript language service, so it allows type safety
+// checking in VSCode.
+
+const ASSETS = "$asset$";
+
+/**
+ * @param {string} configText
+ * @param {Array<string>} rootNames
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function main(configText, rootNames) {
+ ops = Deno.core.ops();
+ println(`>>> ts version ${ts.version}`);
+ println(`>>> rootNames ${rootNames}`);
+
+ const host = new Host();
+
+ assert(rootNames.length > 0);
+
+ const { options, diagnostics } = configure(configText);
+ handleDiagnostics(host, diagnostics);
+
+ println(`>>> TS config: ${JSON.stringify(options)}`);
+
+ const program = ts.createProgram(rootNames, options, host);
+
+ handleDiagnostics(
+ host,
+ ts.getPreEmitDiagnostics(program).filter(({ code }) => {
+ // TS1063: An export assignment cannot be used in a namespace.
+ if (code === 1063) return false;
+ // TS2691: An import path cannot end with a '.ts' extension. Consider
+ // importing 'bad-module' instead.
+ if (code === 2691) return false;
+ // TS5009: Cannot find the common subdirectory path for the input files.
+ if (code === 5009) return false;
+ return true;
+ })
+ );
+
+ const emitResult = program.emit();
+ handleDiagnostics(host, emitResult.diagnostics);
+
+ dispatch(
+ "setEmitResult",
+ Object.assign(emitResult, { tsVersion: ts.version })
+ );
+}
+
+/**
+ * @param {...string} s
+ */
+function println(...s) {
+ Deno.core.print(s.join(" ") + "\n");
+}
+
+/**
+ * @returns {never}
+ */
+function unreachable() {
+ throw Error("unreachable");
+}
+
+/**
+ * @param {unknown} cond
+ */
+function assert(cond) {
+ if (!cond) {
+ throw Error("assert");
+ }
+}
+
+/**
+ * @param {Uint8Array | null} ui8
+ */
+function decodeAscii(ui8) {
+ let out = "";
+ if (!ui8) {
+ return out;
+ }
+ for (let i = 0; i < ui8.length; i++) {
+ out += String.fromCharCode(ui8[i]);
+ }
+ return out;
+}
+
+/**
+ * @param {string} str
+ */
+function encode(str) {
+ const charCodes = str.split("").map(c => c.charCodeAt(0));
+ const ui8 = new Uint8Array(charCodes);
+ return ui8;
+}
+
+//
+/** **Warning!** Op ids must be acquired from Rust using `Deno.core.ops()`
+ * before dispatching any action.
+ * @type {Record<string, number>}
+ */
+let ops;
+
+/**
+ * @type {Map<string, string>}
+ */
+const moduleMap = new Map();
+
+const externalSpecifierRegEx = /^file:\/{3}\S+\/js(\/\S+\.ts)$/;
+
+/**
+ * This is a minimal implementation of a compiler host to be able to allow the
+ * creation of runtime bundles. Some of the methods are implemented in a way
+ * to just appease the TypeScript compiler, not to necessarily be a general
+ * purpose implementation.
+ *
+ * @implements {ts.CompilerHost}
+ */
+class Host {
+ /**
+ * @param {string} _fileName
+ */
+ fileExists(_fileName) {
+ return true;
+ }
+
+ /**
+ * @param {string} _fileName
+ */
+ readFile(_fileName) {
+ unreachable();
+ return undefined;
+ }
+
+ useCaseSensitiveFileNames() {
+ return false;
+ }
+
+ /**
+ * @param {ts.CompilerOptions} _options
+ */
+ getDefaultLibFileName(_options) {
+ return "lib.deno_core.d.ts";
+ }
+
+ getDefaultLibLocation() {
+ return ASSETS;
+ }
+
+ getCurrentDirectory() {
+ return ".";
+ }
+
+ /**
+ * @param {string} fileName
+ * @param {ts.ScriptTarget} languageVersion
+ * @param {(message: string) => void} _onError
+ * @param {boolean} shouldCreateNewSourceFile
+ */
+ getSourceFile(
+ fileName,
+ languageVersion,
+ _onError,
+ shouldCreateNewSourceFile
+ ) {
+ assert(!shouldCreateNewSourceFile); // We haven't yet encountered this.
+
+ // This hacks around the fact that TypeScript tries to magically guess the
+ // d.ts filename.
+ if (fileName.startsWith("$typeRoots$")) {
+ assert(fileName.startsWith("$typeRoots$/"));
+ assert(fileName.endsWith("/index.d.ts"));
+ fileName = fileName
+ .replace("$typeRoots$/", "")
+ .replace("/index.d.ts", "");
+ }
+
+ // This looks up any modules that have been mapped to internal names
+ if (moduleMap.has(fileName)) {
+ fileName = moduleMap.get(fileName);
+ }
+
+ const { sourceCode, moduleName } = dispatch("readFile", {
+ fileName,
+ languageVersion,
+ shouldCreateNewSourceFile
+ });
+
+ // If we match the external specifier regex, we will then create an internal
+ // specifier and then use that when creating the source file
+ let internalModuleName = moduleName;
+ const result = externalSpecifierRegEx.exec(moduleName);
+ if (result) {
+ const [, specifier] = result;
+ const internalSpecifier = `$deno$${specifier}`;
+ moduleMap.set(internalSpecifier, moduleName);
+ internalModuleName = internalSpecifier;
+ }
+
+ const sourceFile = ts.createSourceFile(
+ internalModuleName,
+ sourceCode,
+ languageVersion
+ );
+ sourceFile.moduleName = internalModuleName;
+ return sourceFile;
+ }
+
+ /**
+ * @param {string} fileName
+ * @param {string} data
+ * @param {boolean} _writeByteOrderMark
+ * @param {((message: string) => void)?} _onError
+ * @param {ReadonlyArray<ts.SourceFile>?} sourceFiles
+ */
+ writeFile(
+ fileName,
+ data,
+ _writeByteOrderMark,
+ _onError = null,
+ sourceFiles = null
+ ) {
+ if (sourceFiles == null) {
+ return;
+ }
+ const moduleName = sourceFiles[sourceFiles.length - 1].moduleName;
+ return dispatch("writeFile", { fileName, moduleName, data });
+ }
+
+ /**
+ * @param {string} _fileName
+ * @param {ts.Path} _path
+ * @param {ts.ScriptTarget} _languageVersion
+ * @param {*} _onError
+ * @param {boolean} _shouldCreateNewSourceFile
+ */
+ getSourceFileByPath(
+ _fileName,
+ _path,
+ _languageVersion,
+ _onError,
+ _shouldCreateNewSourceFile
+ ) {
+ unreachable();
+ return undefined;
+ }
+
+ /**
+ * @param {string} fileName
+ */
+ getCanonicalFileName(fileName) {
+ return fileName;
+ }
+
+ getNewLine() {
+ return "\n";
+ }
+
+ /**
+ * @param {string[]} moduleNames
+ * @param {string} containingFile
+ * @return {Array<ts.ResolvedModule | undefined>}
+ */
+ resolveModuleNames(moduleNames, containingFile) {
+ // If the containing file is an internal specifier, map it back to the
+ // external specifier
+ containingFile = moduleMap.has(containingFile)
+ ? moduleMap.get(containingFile)
+ : containingFile;
+ /** @type {string[]} */
+ const resolvedNames = dispatch("resolveModuleNames", {
+ moduleNames,
+ containingFile
+ });
+ /** @type {ts.ResolvedModule[]} */
+ const r = resolvedNames.map(resolvedFileName => {
+ const extension = getExtension(resolvedFileName);
+ return { resolvedFileName, extension };
+ });
+ return r;
+ }
+}
+
+/**
+ * @param {string} configurationText
+ */
+function configure(configurationText) {
+ const { config, error } = ts.parseConfigFileTextToJson(
+ "tsconfig.json",
+ configurationText
+ );
+ if (error) {
+ return { options: {}, diagnostics: [error] };
+ }
+ const { options, errors } = ts.convertCompilerOptionsFromJson(
+ config.compilerOptions,
+ ""
+ );
+ return {
+ options,
+ diagnostics: errors.length ? errors : undefined
+ };
+}
+
+/**
+ * @param {string} opName
+ * @param {Record<string,any>} obj
+ */
+function dispatch(opName, obj) {
+ const opId = ops[opName];
+
+ if (!opId) {
+ throw new Error(`Unknown op: ${opName}`);
+ }
+
+ const s = JSON.stringify(obj);
+ const msg = encode(s);
+ const resUi8 = Deno.core.dispatch(opId, msg);
+ const resStr = decodeAscii(resUi8);
+ const res = JSON.parse(resStr);
+ if (!res["ok"]) {
+ throw Error(`${opName} failed ${res["err"]}. Args: ${JSON.stringify(obj)}`);
+ }
+ return res["ok"];
+}
+
+/**
+ * @param {number} code
+ */
+function exit(code) {
+ dispatch("exit", { code });
+ return unreachable();
+}
+
+// Maximum number of diagnostics to display.
+const MAX_ERRORS = 5;
+
+/**
+ * @param {ts.CompilerHost} host
+ * @param {ReadonlyArray<ts.Diagnostic> | undefined} diagnostics
+ */
+function handleDiagnostics(host, diagnostics) {
+ if (diagnostics && diagnostics.length) {
+ let rest = 0;
+ if (diagnostics.length > MAX_ERRORS) {
+ rest = diagnostics.length - MAX_ERRORS;
+ diagnostics = diagnostics.slice(0, MAX_ERRORS);
+ }
+ const msg = ts.formatDiagnosticsWithColorAndContext(diagnostics, host);
+ println(msg);
+ if (rest) {
+ println(`And ${rest} other errors.`);
+ }
+ exit(1);
+ }
+}
+
+/** Returns the TypeScript Extension enum for a given media type.
+ * @param {string} fileName
+ * @returns {ts.Extension}
+ */
+function getExtension(fileName) {
+ if (fileName.endsWith(".d.ts")) {
+ return ts.Extension.Dts;
+ } else if (fileName.endsWith(".ts")) {
+ return ts.Extension.Ts;
+ } else if (fileName.endsWith(".js")) {
+ return ts.Extension.Js;
+ } else {
+ throw TypeError(`Cannot resolve extension for ${fileName}`);
+ }
+}
diff --git a/deno_typescript/lib.deno_core.d.ts b/deno_typescript/lib.deno_core.d.ts
new file mode 100644
index 000000000..c3a20eb08
--- /dev/null
+++ b/deno_typescript/lib.deno_core.d.ts
@@ -0,0 +1,66 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+
+// This file contains APIs that are introduced into the global namespace by
+// Deno core. These are not intended to be used directly by runtime users of
+// Deno and therefore do not flow through to the runtime type library.
+
+declare interface MessageCallback {
+ (opId: number, msg: Uint8Array): void;
+}
+
+interface EvalErrorInfo {
+ // Is the object thrown a native Error?
+ isNativeError: boolean;
+ // Was the error happened during compilation?
+ isCompileError: boolean;
+ // The actual thrown entity
+ // (might be an Error or anything else thrown by the user)
+ // If isNativeError is true, this is an Error
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ thrown: any;
+}
+
+declare interface DenoCore {
+ print(s: string, isErr?: boolean);
+ dispatch(
+ opId: number,
+ control: Uint8Array,
+ zeroCopy?: ArrayBufferView | null
+ ): Uint8Array | null;
+ setAsyncHandler(cb: MessageCallback): void;
+ sharedQueue: {
+ head(): number;
+ numRecords(): number;
+ size(): number;
+ push(buf: Uint8Array): boolean;
+ reset(): void;
+ shift(): Uint8Array | null;
+ };
+
+ ops(): Record<string, number>;
+
+ recv(cb: MessageCallback): void;
+
+ send(
+ opId: number,
+ control: null | ArrayBufferView,
+ data?: ArrayBufferView
+ ): null | Uint8Array;
+
+ shared: SharedArrayBuffer;
+
+ /** Evaluate provided code in the current context.
+ * It differs from eval(...) in that it does not create a new context.
+ * Returns an array: [output, errInfo].
+ * If an error occurs, `output` becomes null and `errInfo` is non-null.
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ evalContext(code: string): [any, EvalErrorInfo | null];
+
+ errorToJSON: (e: Error) => string;
+}
+
+declare interface DenoInterface {
+ core: DenoCore;
+}
+declare let Deno: DenoInterface;
diff --git a/deno_typescript/lib.rs b/deno_typescript/lib.rs
new file mode 100644
index 000000000..d130ee4c6
--- /dev/null
+++ b/deno_typescript/lib.rs
@@ -0,0 +1,299 @@
+// 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::CoreOp;
+use deno::ErrBox;
+use deno::Isolate;
+use deno::ModuleSpecifier;
+use deno::PinnedBuf;
+use deno::StartupData;
+pub use ops::EmitResult;
+use ops::WrittenFile;
+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!("typescript/lib/typescript.js");
+static COMPILER_CODE: &str = include_str!("compiler_main.js");
+static AMD_RUNTIME_CODE: &str = include_str!("amd_runtime.js");
+
+pub fn ts_version() -> String {
+ let data = include_str!("typescript/package.json");
+ let pkg: serde_json::Value = serde_json::from_str(data).unwrap();
+ pkg["version"].as_str().unwrap().to_string()
+}
+
+#[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()
+ }
+}
+
+fn compiler_op<D>(
+ ts_state: Arc<Mutex<TSState>>,
+ dispatcher: D,
+) -> impl Fn(&[u8], Option<PinnedBuf>) -> CoreOp
+where
+ D: Fn(&mut TSState, &[u8]) -> CoreOp,
+{
+ move |control: &[u8], zero_copy_buf: Option<PinnedBuf>| -> CoreOp {
+ assert!(zero_copy_buf.is_none()); // zero_copy_buf unused in compiler.
+ let mut s = ts_state.lock().unwrap();
+ dispatcher(&mut s, control)
+ }
+}
+
+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(),
+ }));
+
+ isolate.register_op(
+ "readFile",
+ compiler_op(state.clone(), ops::json_op(ops::read_file)),
+ );
+ isolate
+ .register_op("exit", compiler_op(state.clone(), ops::json_op(ops::exit)));
+ isolate.register_op(
+ "writeFile",
+ compiler_op(state.clone(), ops::json_op(ops::write_file)),
+ );
+ isolate.register_op(
+ "resolveModuleNames",
+ compiler_op(state.clone(), ops::json_op(ops::resolve_module_names)),
+ );
+ isolate.register_op(
+ "setEmitResult",
+ compiler_op(state.clone(), ops::json_op(ops::set_emit_result)),
+ );
+
+ 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);
+ 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(())
+}
+
+/// 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> {
+ macro_rules! inc {
+ ($e:expr) => {
+ Some(include_str!(concat!("typescript/lib/", $e)))
+ };
+ }
+ match name {
+ "lib.deno_core.d.ts" => Some(include_str!("lib.deno_core.d.ts")),
+ "typescript.d.ts" => inc!("typescript.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.asyncgenerator.d.ts" => inc!("lib.es2018.asyncgenerator.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]);
+}
diff --git a/deno_typescript/ops.rs b/deno_typescript/ops.rs
new file mode 100644
index 000000000..7e7d24c72
--- /dev/null
+++ b/deno_typescript/ops.rs
@@ -0,0 +1,137 @@
+use crate::TSState;
+use deno::CoreOp;
+use deno::ErrBox;
+use deno::ModuleSpecifier;
+use deno::Op;
+use serde::Deserialize;
+use serde_json::json;
+use serde_json::Value;
+
+#[derive(Debug)]
+pub struct WrittenFile {
+ pub url: String,
+ pub module_name: String,
+ pub source_code: String,
+}
+
+type Dispatcher = fn(state: &mut TSState, args: Value) -> Result<Value, ErrBox>;
+
+pub fn json_op(d: Dispatcher) -> impl Fn(&mut TSState, &[u8]) -> CoreOp {
+ move |state: &mut TSState, control: &[u8]| {
+ let result = serde_json::from_slice(control)
+ .map_err(ErrBox::from)
+ .and_then(move |args| d(state, args));
+
+ let response = match result {
+ Ok(v) => json!({ "ok": v }),
+ Err(err) => json!({ "err": err.to_string() }),
+ };
+
+ let x = serde_json::to_string(&response).unwrap();
+ let vec = x.into_bytes();
+ Op::Sync(vec.into_boxed_slice())
+ }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ReadFile {
+ file_name: String,
+ language_version: Option<i32>,
+ should_create_new_source_file: bool,
+}
+
+pub fn read_file(_s: &mut TSState, v: Value) -> Result<Value, ErrBox> {
+ let v: ReadFile = serde_json::from_value(v)?;
+ let (module_name, source_code) = if v.file_name.starts_with("$asset$/") {
+ let asset = v.file_name.replace("$asset$/", "");
+ let source_code = crate::get_asset2(&asset)?.to_string();
+ (asset, source_code)
+ } else {
+ assert!(!v.file_name.starts_with("$assets$"), "you meant $asset$");
+ let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.file_name)?;
+ let path = module_specifier.as_url().to_file_path().unwrap();
+ println!("cargo:rerun-if-changed={}", path.display());
+ (
+ module_specifier.as_str().to_string(),
+ std::fs::read_to_string(&path)?,
+ )
+ };
+ Ok(json!({
+ "moduleName": module_name,
+ "sourceCode": source_code,
+ }))
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct WriteFile {
+ file_name: String,
+ data: String,
+ module_name: String,
+}
+
+pub fn write_file(s: &mut TSState, v: Value) -> Result<Value, ErrBox> {
+ let v: WriteFile = serde_json::from_value(v)?;
+ let module_specifier = ModuleSpecifier::resolve_url_or_path(&v.file_name)?;
+ if s.bundle {
+ std::fs::write(&v.file_name, &v.data)?;
+ }
+ s.written_files.push(WrittenFile {
+ url: module_specifier.as_str().to_string(),
+ module_name: v.module_name,
+ source_code: v.data,
+ });
+ Ok(json!(true))
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct ResolveModuleNames {
+ module_names: Vec<String>,
+ containing_file: String,
+}
+
+pub fn resolve_module_names(
+ _s: &mut TSState,
+ v: Value,
+) -> Result<Value, ErrBox> {
+ let v: ResolveModuleNames = serde_json::from_value(v).unwrap();
+ let mut resolved = Vec::<String>::new();
+ let referrer = ModuleSpecifier::resolve_url_or_path(&v.containing_file)?;
+ for specifier in v.module_names {
+ if specifier.starts_with("$asset$/") {
+ resolved.push(specifier.clone());
+ } else {
+ let ms = ModuleSpecifier::resolve_import(&specifier, referrer.as_str())?;
+ resolved.push(ms.as_str().to_string());
+ }
+ }
+ Ok(json!(resolved))
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct Exit {
+ code: i32,
+}
+
+pub fn exit(s: &mut TSState, v: Value) -> Result<Value, ErrBox> {
+ let v: Exit = serde_json::from_value(v)?;
+ s.exit_code = v.code;
+ std::process::exit(v.code)
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct EmitResult {
+ pub emit_skipped: bool,
+ pub diagnostics: Vec<String>,
+ pub emitted_files: Vec<String>,
+}
+
+pub fn set_emit_result(s: &mut TSState, v: Value) -> Result<Value, ErrBox> {
+ let v: EmitResult = serde_json::from_value(v)?;
+ s.emit_result = Some(v);
+ Ok(json!(true))
+}
diff --git a/deno_typescript/typescript b/deno_typescript/typescript
new file mode 160000
+Subproject 26655db1dd04d93217002ac87f950aba812c53e