diff --git a/cli/compilers/ts.rs b/cli/compilers/ts.rs index 532791cd00..9b6661f588 100644 --- a/cli/compilers/ts.rs +++ b/cli/compilers/ts.rs @@ -454,6 +454,7 @@ impl TsCompiler { filename: compiled_code_filename, media_type: msg::MediaType::JavaScript, source_code: compiled_code, + types_url: None, }; Ok(compiled_module) @@ -521,6 +522,7 @@ impl TsCompiler { filename: source_map_filename, media_type: msg::MediaType::JavaScript, source_code, + types_url: None, }; Ok(source_map_file) @@ -694,6 +696,7 @@ mod tests { filename: PathBuf::from(p.to_str().unwrap().to_string()), media_type: msg::MediaType::TypeScript, source_code: include_bytes!("../tests/002_hello.ts").to_vec(), + types_url: None, }; let mock_state = ThreadSafeGlobalState::mock(vec![ diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index b5c00c3070..c891665093 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -6,12 +6,14 @@ use crate::disk_cache::DiskCache; use crate::http_util; use crate::http_util::create_http_client; use crate::http_util::FetchOnceResult; +use crate::http_util::ResultPayload; use crate::msg; use crate::progress::Progress; use deno_core::ErrBox; use deno_core::ModuleSpecifier; use futures::future::Either; use futures::future::FutureExt; +use regex::Regex; use reqwest; use serde_json; use std; @@ -58,6 +60,7 @@ pub fn source_cache_failed_error(module_name: &str, reason: &str) -> ErrBox { pub struct SourceFile { pub url: Url, pub filename: PathBuf, + pub types_url: Option, pub media_type: msg::MediaType, pub source_code: Vec, } @@ -299,11 +302,18 @@ impl SourceFileFetcher { }; let media_type = map_content_type(&filepath, None); + let types_url = match media_type { + msg::MediaType::JavaScript | msg::MediaType::JSX => { + get_types_url(&module_url, &source_code, None) + } + _ => None, + }; Ok(SourceFile { url: module_url.clone(), filename: filepath, media_type, source_code, + types_url, }) } @@ -362,11 +372,23 @@ impl SourceFileFetcher { &filepath, source_code_headers.mime_type.as_ref().map(String::as_str), ); + let types_url = match media_type { + msg::MediaType::JavaScript | msg::MediaType::JSX => get_types_url( + &module_url, + &source_code, + source_code_headers + .x_typescript_types + .as_ref() + .map(String::as_str), + ), + _ => None, + }; Ok(Some(SourceFile { url: module_url.clone(), filename: filepath, media_type, source_code, + types_url, })) } @@ -443,6 +465,7 @@ impl SourceFileFetcher { None, Some(new_module_url.to_string()), None, + None, ) { return Err(source_header_cache_failed_error( module_url.as_str(), @@ -463,13 +486,19 @@ impl SourceFileFetcher { ) .await } - FetchOnceResult::Code(source, maybe_content_type, etag) => { + FetchOnceResult::Code(ResultPayload { + body: source, + content_type: maybe_content_type, + etag, + x_typescript_types, + }) => { // We land on the code. if let Err(e) = dir.save_source_code_headers( &module_url, maybe_content_type.clone(), None, etag, + x_typescript_types.clone(), ) { return Err(source_header_cache_failed_error( module_url.as_str(), @@ -494,11 +523,21 @@ impl SourceFileFetcher { maybe_content_type.as_ref().map(String::as_str), ); + let types_url = match media_type { + msg::MediaType::JavaScript | msg::MediaType::JSX => get_types_url( + &module_url, + source.as_bytes(), + x_typescript_types.as_ref().map(String::as_str), + ), + _ => None, + }; + let source_file = SourceFile { url: module_url.clone(), filename: filepath, media_type, source_code: source.as_bytes().to_owned(), + types_url, }; // Explicit drop to keep reference alive until future completes. @@ -554,6 +593,7 @@ impl SourceFileFetcher { mime_type: Option, redirect_to: Option, etag: Option, + x_typescript_types: Option, ) -> std::io::Result<()> { let cache_key = self .deps_cache @@ -567,6 +607,7 @@ impl SourceFileFetcher { mime_type, redirect_to, etag, + x_typescript_types, }; let cache_filename = self.deps_cache.get_cache_filename(url); @@ -648,6 +689,41 @@ fn map_js_like_extension( } } +/// Take a module URL and source code and determines if the source code contains +/// a type directive, and if so, returns the parsed URL for that type directive. +fn get_types_url( + module_url: &Url, + source_code: &[u8], + maybe_types_header: Option<&str>, +) -> Option { + lazy_static! { + /// Matches reference type directives in strings, which provide + /// type files that should be used by the compiler instead of the + /// JavaScript file. + static ref DIRECTIVE_TYPES: Regex = Regex::new( + r#"(?m)^/{3}\s*"# + ) + .unwrap(); + } + + match maybe_types_header { + Some(types_header) => match Url::parse(&types_header) { + Ok(url) => Some(url), + _ => Some(module_url.join(&types_header).unwrap()), + }, + _ => match DIRECTIVE_TYPES.captures(str::from_utf8(source_code).unwrap()) { + Some(cap) => { + let val = cap.get(1).unwrap().as_str(); + match Url::parse(&val) { + Ok(url) => Some(url), + _ => Some(module_url.join(&val).unwrap()), + } + } + _ => None, + }, + } +} + fn filter_shebang(bytes: Vec) -> Vec { let string = str::from_utf8(&bytes).unwrap(); if let Some(i) = string.find('\n') { @@ -690,11 +766,14 @@ pub struct SourceCodeHeaders { pub redirect_to: Option, /// ETag of the remote source file pub etag: Option, + /// X-TypeScript-Types defines the location of a .d.ts file + pub x_typescript_types: Option, } static MIME_TYPE: &str = "mime_type"; static REDIRECT_TO: &str = "redirect_to"; static ETAG: &str = "etag"; +static X_TYPESCRIPT_TYPES: &str = "x_typescript_types"; impl SourceCodeHeaders { pub fn from_json_string(headers_string: String) -> Self { @@ -706,11 +785,14 @@ impl SourceCodeHeaders { let mime_type = headers_json[MIME_TYPE].as_str().map(String::from); let redirect_to = headers_json[REDIRECT_TO].as_str().map(String::from); let etag = headers_json[ETAG].as_str().map(String::from); + let x_typescript_types = + headers_json[X_TYPESCRIPT_TYPES].as_str().map(String::from); return SourceCodeHeaders { mime_type, redirect_to, etag, + x_typescript_types, }; } @@ -751,6 +833,11 @@ impl SourceCodeHeaders { value_map.insert(ETAG.to_string(), json!(etag)); } + if let Some(x_typescript_types) = &self.x_typescript_types { + value_map + .insert(X_TYPESCRIPT_TYPES.to_string(), json!(x_typescript_types)); + } + if value_map.is_empty() { return Ok(None); } @@ -878,12 +965,14 @@ mod tests { assert_eq!(headers.mime_type.clone().unwrap(), "text/javascript"); assert_eq!(headers.redirect_to.unwrap(), "http://example.com/a.js"); assert_eq!(headers.etag, None); + assert_eq!(headers.x_typescript_types, None); let _ = fetcher.save_source_code_headers( &url, Some("text/typescript".to_owned()), Some("http://deno.land/a.js".to_owned()), Some("W/\"04572f4749af993f4961a7e5daa1e4d5\"".to_owned()), + Some("./a.d.ts".to_owned()), ); let headers2 = fetcher.get_source_code_headers(&url); assert_eq!(headers2.mime_type.clone().unwrap(), "text/typescript"); @@ -892,6 +981,7 @@ mod tests { headers2.etag.unwrap(), "W/\"04572f4749af993f4961a7e5daa1e4d5\"" ); + assert_eq!(headers2.x_typescript_types.unwrap(), "./a.d.ts") } #[test] @@ -971,6 +1061,7 @@ mod tests { Some("application/json".to_owned()), None, None, + None, ); fetcher_2.get_source_file_async(&module_url_1, true, false, false) }) @@ -1047,6 +1138,7 @@ mod tests { Some("text/typescript".to_owned()), None, None, + None, ); fetcher.get_source_file_async(&module_url, true, false, false) }) @@ -1416,6 +1508,7 @@ mod tests { Some("text/javascript".to_owned()), None, None, + None, ); let result2 = fetcher.fetch_cached_remote_source(&module_url); assert!(result2.is_ok()); @@ -1459,6 +1552,7 @@ mod tests { Some("text/javascript".to_owned()), None, None, + None, ); let result2 = fetcher.fetch_cached_remote_source(&module_url); assert!(result2.is_ok()); @@ -1590,6 +1684,22 @@ mod tests { )); } + #[test] + fn test_fetch_source_file_2() { + /*recompile ts file*/ + let (_temp_dir, fetcher) = test_setup(); + + let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/001_hello.js"); + let specifier = + ModuleSpecifier::resolve_url_or_path(p.to_str().unwrap()).unwrap(); + tokio_util::run(fetcher.fetch_source_file_async(&specifier, None).map( + |r| { + assert!(r.is_ok()); + }, + )); + } + #[test] fn test_resolve_module_3() { // unsupported schemes @@ -1841,7 +1951,7 @@ mod tests { assert!(source.is_ok()); let source = source.unwrap(); assert_eq!(source.source_code, b"console.log('etag')"); - assert_eq!(&(source.media_type), &msg::MediaType::JavaScript); + assert_eq!(&(source.media_type), &msg::MediaType::TypeScript); let headers = fetcher.get_source_code_headers(&module_url); assert_eq!(headers.etag, Some("33a64df551425fcc55e".to_string())); @@ -1875,4 +1985,136 @@ mod tests { tokio_util::run(fut); drop(http_server_guard); } + + #[test] + fn test_get_types_url_1() { + let module_url = Url::parse("https://example.com/mod.js").unwrap(); + let source_code = b"console.log(\"foo\");".to_owned(); + let result = get_types_url(&module_url, &source_code, None); + assert_eq!(result, None); + } + + #[test] + fn test_get_types_url_2() { + let module_url = Url::parse("https://example.com/mod.js").unwrap(); + let source_code = r#"/// + console.log("foo");"# + .as_bytes() + .to_owned(); + let result = get_types_url(&module_url, &source_code, None); + assert_eq!( + result, + Some(Url::parse("https://example.com/mod.d.ts").unwrap()) + ); + } + + #[test] + fn test_get_types_url_3() { + let module_url = Url::parse("https://example.com/mod.js").unwrap(); + let source_code = r#"/// + console.log("foo");"# + .as_bytes() + .to_owned(); + let result = get_types_url(&module_url, &source_code, None); + assert_eq!( + result, + Some(Url::parse("https://deno.land/mod.d.ts").unwrap()) + ); + } + + #[test] + fn test_get_types_url_4() { + let module_url = Url::parse("file:///foo/bar/baz.js").unwrap(); + let source_code = r#"/// + console.log("foo");"# + .as_bytes() + .to_owned(); + let result = get_types_url(&module_url, &source_code, None); + assert_eq!( + result, + Some(Url::parse("file:///foo/qat/baz.d.ts").unwrap()) + ); + } + + #[test] + fn test_get_types_url_5() { + let module_url = Url::parse("https://example.com/mod.js").unwrap(); + let source_code = b"console.log(\"foo\");".to_owned(); + let result = get_types_url(&module_url, &source_code, Some("./mod.d.ts")); + assert_eq!( + result, + Some(Url::parse("https://example.com/mod.d.ts").unwrap()) + ); + } + + #[test] + fn test_get_types_url_6() { + let module_url = Url::parse("https://example.com/mod.js").unwrap(); + let source_code = r#"/// + console.log("foo");"# + .as_bytes() + .to_owned(); + let result = get_types_url( + &module_url, + &source_code, + Some("https://deno.land/mod.d.ts"), + ); + assert_eq!( + result, + Some(Url::parse("https://deno.land/mod.d.ts").unwrap()) + ); + } + + #[test] + fn test_fetch_with_types_header() { + let http_server_guard = crate::test_util::http_server(); + let (_temp_dir, fetcher) = test_setup(); + let module_url = + Url::parse("http://127.0.0.1:4545/xTypeScriptTypes.js").unwrap(); + + let fut = async move { + let source = fetcher + .fetch_remote_source_async(&module_url, false, false, 1) + .await; + assert!(source.is_ok()); + let source = source.unwrap(); + assert_eq!(source.source_code, b"export const foo = 'foo';"); + assert_eq!(&(source.media_type), &msg::MediaType::JavaScript); + assert_eq!( + source.types_url, + Some( + Url::parse("http://127.0.0.1:4545/xTypeScriptTypes.d.ts").unwrap() + ) + ); + }; + + tokio_util::run(fut); + drop(http_server_guard); + } + + #[test] + fn test_fetch_with_types_reference() { + let http_server_guard = crate::test_util::http_server(); + let (_temp_dir, fetcher) = test_setup(); + let module_url = + Url::parse("http://127.0.0.1:4545/referenceTypes.js").unwrap(); + + let fut = async move { + let source = fetcher + .fetch_remote_source_async(&module_url, false, false, 1) + .await; + assert!(source.is_ok()); + let source = source.unwrap(); + assert_eq!(&(source.media_type), &msg::MediaType::JavaScript); + assert_eq!( + source.types_url, + Some( + Url::parse("http://127.0.0.1:4545/xTypeScriptTypes.d.ts").unwrap() + ) + ); + }; + + tokio_util::run(fut); + drop(http_server_guard); + } } diff --git a/cli/http_util.rs b/cli/http_util.rs index 5d1c7c61dd..466bec3f21 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -7,6 +7,8 @@ use bytes::Bytes; use deno_core::ErrBox; use futures::future::FutureExt; use reqwest; +use reqwest::header::HeaderMap; +use reqwest::header::HeaderValue; use reqwest::header::ACCEPT_ENCODING; use reqwest::header::CONTENT_ENCODING; use reqwest::header::CONTENT_TYPE; @@ -14,7 +16,6 @@ use reqwest::header::ETAG; use reqwest::header::IF_NONE_MATCH; use reqwest::header::LOCATION; use reqwest::header::USER_AGENT; -use reqwest::header::{HeaderMap, HeaderValue}; use reqwest::redirect::Policy; use reqwest::Client; use reqwest::Response; @@ -72,17 +73,24 @@ fn resolve_url_from_location(base_url: &Url, location: &str) -> Url { } } +#[derive(Debug, PartialEq)] +pub struct ResultPayload { + pub body: String, + pub content_type: Option, + pub etag: Option, + pub x_typescript_types: Option, +} + #[derive(Debug, PartialEq)] pub enum FetchOnceResult { - // (code, maybe_content_type, etag) - Code(String, Option, Option), + Code(ResultPayload), NotModified, Redirect(Url), } /// Asynchronously fetches the given HTTP URL one pass only. /// If no redirect is present and no error occurs, -/// yields Code(code, maybe_content_type). +/// yields Code(ResultPayload). /// If redirect occurs, does not follow and /// yields Redirect(url). pub fn fetch_string_once( @@ -145,6 +153,16 @@ pub fn fetch_string_once( .get(CONTENT_ENCODING) .map(|content_encoding| content_encoding.to_str().unwrap().to_owned()); + const X_TYPESCRIPT_TYPES: &str = "X-TypeScript-Types"; + + let x_typescript_types = + response + .headers() + .get(X_TYPESCRIPT_TYPES) + .map(|x_typescript_types| { + x_typescript_types.to_str().unwrap().to_owned() + }); + let body; if let Some(content_encoding) = content_encoding { body = match content_encoding { @@ -161,7 +179,12 @@ pub fn fetch_string_once( body = response.text().await?; } - return Ok(FetchOnceResult::Code(body, content_type, etag)); + return Ok(FetchOnceResult::Code(ResultPayload { + body, + content_type, + etag, + x_typescript_types, + })); }; fut.boxed() @@ -257,10 +280,16 @@ mod tests { let client = create_http_client(); let fut = fetch_string_once(client, &url, None).map(|result| match result { - Ok(FetchOnceResult::Code(code, maybe_content_type, etag)) => { + Ok(FetchOnceResult::Code(ResultPayload { + body: code, + content_type: maybe_content_type, + etag, + x_typescript_types, + })) => { assert!(!code.is_empty()); assert_eq!(maybe_content_type, Some("application/json".to_string())); - assert_eq!(etag, None) + assert_eq!(etag, None); + assert_eq!(x_typescript_types, None); } _ => panic!(), }); @@ -280,7 +309,12 @@ mod tests { let client = create_http_client(); let fut = fetch_string_once(client, &url, None).map(|result| match result { - Ok(FetchOnceResult::Code(code, maybe_content_type, etag)) => { + Ok(FetchOnceResult::Code(ResultPayload { + body: code, + content_type: maybe_content_type, + etag, + x_typescript_types, + })) => { assert!(!code.is_empty()); assert_eq!(code, "console.log('gzip')"); assert_eq!( @@ -288,6 +322,7 @@ mod tests { Some("application/javascript".to_string()) ); assert_eq!(etag, None); + assert_eq!(x_typescript_types, None); } _ => panic!(), }); @@ -304,14 +339,20 @@ mod tests { let fut = async move { fetch_string_once(client.clone(), &url, None) .map(|result| match result { - Ok(FetchOnceResult::Code(code, maybe_content_type, etag)) => { + Ok(FetchOnceResult::Code(ResultPayload { + body: code, + content_type: maybe_content_type, + etag, + x_typescript_types, + })) => { assert!(!code.is_empty()); assert_eq!(code, "console.log('etag')"); assert_eq!( maybe_content_type, - Some("application/javascript".to_string()) + Some("application/typescript".to_string()) ); assert_eq!(etag, Some("33a64df551425fcc55e".to_string())); + assert_eq!(x_typescript_types, None); } _ => panic!(), }) @@ -341,7 +382,12 @@ mod tests { let client = create_http_client(); let fut = fetch_string_once(client, &url, None).map(|result| match result { - Ok(FetchOnceResult::Code(code, maybe_content_type, etag)) => { + Ok(FetchOnceResult::Code(ResultPayload { + body: code, + content_type: maybe_content_type, + etag, + x_typescript_types, + })) => { assert!(!code.is_empty()); assert_eq!(code, "console.log('brotli');"); assert_eq!( @@ -349,6 +395,7 @@ mod tests { Some("application/javascript".to_string()) ); assert_eq!(etag, None); + assert_eq!(x_typescript_types, None); } _ => panic!(), }); diff --git a/cli/ops/compiler.rs b/cli/ops/compiler.rs index d2a2a1d58c..2934c79eeb 100644 --- a/cli/ops/compiler.rs +++ b/cli/ops/compiler.rs @@ -109,8 +109,20 @@ fn op_fetch_source_files( let files = try_join_all(futures).await?; // We want to get an array of futures that resolves to - let v = files.into_iter().map(|file| { + let v = files.into_iter().map(|f| { async { + // if the source file contains a `types_url` we need to replace + // the module with the type definition when requested by the compiler + let file = match f.types_url { + Some(types_url) => { + let types_specifier = ModuleSpecifier::from(types_url); + global_state + .file_fetcher + .fetch_source_file_async(&types_specifier, ref_specifier.clone()) + .await? + } + _ => f, + }; // Special handling of Wasm files: // compile them into JS first! // This allows TS to do correct export types. diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index 52fc254c5d..7f318119ad 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -622,6 +622,17 @@ itest!(type_definitions { output: "type_definitions.ts.out", }); +itest!(type_directives_01 { + args: "run --reload -L debug type_directives_01.ts", + output: "type_directives_01.ts.out", + http_server: true, +}); + +itest!(type_directives_02 { + args: "run --reload -L debug type_directives_02.ts", + output: "type_directives_02.ts.out", +}); + itest!(types { args: "types", output: "types.out", diff --git a/cli/tests/subdir/type_reference.d.ts b/cli/tests/subdir/type_reference.d.ts new file mode 100644 index 0000000000..f9b8de5ede --- /dev/null +++ b/cli/tests/subdir/type_reference.d.ts @@ -0,0 +1 @@ +export const foo: "foo"; diff --git a/cli/tests/subdir/type_reference.js b/cli/tests/subdir/type_reference.js new file mode 100644 index 0000000000..917d891980 --- /dev/null +++ b/cli/tests/subdir/type_reference.js @@ -0,0 +1,3 @@ +/// + +export const foo = "foo"; diff --git a/cli/tests/type_directives_01.ts b/cli/tests/type_directives_01.ts new file mode 100644 index 0000000000..71305824c7 --- /dev/null +++ b/cli/tests/type_directives_01.ts @@ -0,0 +1,3 @@ +import * as foo from "http://127.0.0.1:4545/xTypeScriptTypes.js"; + +console.log(foo.foo); diff --git a/cli/tests/type_directives_01.ts.out b/cli/tests/type_directives_01.ts.out new file mode 100644 index 0000000000..f2fbdb15ad --- /dev/null +++ b/cli/tests/type_directives_01.ts.out @@ -0,0 +1,3 @@ +[WILDCARD] +DEBUG TS - compiler::host.getSourceFile http://127.0.0.1:4545/xTypeScriptTypes.d.ts +[WILDCARD] \ No newline at end of file diff --git a/cli/tests/type_directives_02.ts b/cli/tests/type_directives_02.ts new file mode 100644 index 0000000000..f7274bf26d --- /dev/null +++ b/cli/tests/type_directives_02.ts @@ -0,0 +1,3 @@ +import * as foo from "./subdir/type_reference.js"; + +console.log(foo.foo); diff --git a/cli/tests/type_directives_02.ts.out b/cli/tests/type_directives_02.ts.out new file mode 100644 index 0000000000..1e630ca32d --- /dev/null +++ b/cli/tests/type_directives_02.ts.out @@ -0,0 +1,3 @@ +[WILDCARD] +DEBUG TS - compiler::host.getSourceFile file:[WILDCARD]cli/tests/subdir/type_reference.d.ts +[WILDCARD] \ No newline at end of file diff --git a/std/manual.md b/std/manual.md index 3ddf6493ba..2fa93718f1 100644 --- a/std/manual.md +++ b/std/manual.md @@ -567,20 +567,65 @@ The out of the box TypeScript compiler though relies on both extension-less modules and the Node.js module resolution logic to apply types to JavaScript modules. -In order to bridge this gap, Deno supports compiler hints that inform Deno the -location of `.d.ts` files and the JavaScript code they relate to. A compiler -hint looks like this: +In order to bridge this gap, Deno supports three ways of referencing type +definition files without having to resort to "magic" resolution. + +#### Compiler hint + +If you are importing a JavaScript module, and you know where the type definition +for that module is located, you can specify the type definition at import. This +takes the form of a compiler hint. Compiler hints inform Deno the location of +`.d.ts` files and the JavaScript code that is imported that they relate to. The +hint is `@deno-types` and when specified the value will be used in the compiler +instead of the JavaScript module. For example if you had `foo.js`, but you know +that along side of it was `foo.d.ts` which was the types for the file, the code +would look like this: ```ts // @deno-types="./foo.d.ts" import * as foo from "./foo.js"; ``` -Where the hint affects the next `import` statement (or `export ... from` -statement) where the value of the `@deno-types` will be substituted at compile -time instead of the specified module. Like in the above example, the Deno -compiler will load `./foo.d.ts` instead of `./foo.js`. Deno will still load -`./foo.js` when it runs the program. +The value follows the same resolution logic as importing a module, meaning the +file needs to have an extension and is relative to the current module. Remote +specifiers are also allowed. + +The hint affects the next `import` statement (or `export ... from` statement) +where the value of the `@deno-types` will be substituted at compile time instead +of the specified module. Like in the above example, the Deno compiler will load +`./foo.d.ts` instead of `./foo.js`. Deno will still load `./foo.js` when it runs +the program. + +#### Triple-slash reference directive in JavaScript files + +If you are hosting modules which you want to be consumed by Deno, and you want +to inform Deno the location of the type definitions, you can utilise a +triple-slash directive in the actual code. For example, if you have a JavaScript +module, where you want to provide Deno with the location of the type definitions +for that JavaScript file, which happens to be along side that file. You +JavaScript module named `foo.js` might look like this: + +```js +/// +export const foo = "foo"; +``` + +Deno will see this, and the compiler will use `foo.d.ts` when type checking the +file, though `foo.js` will be loaded at runtime. The resolution of the value of +the directive follows the same resolution logic as importing a module, meaning +the file needs to have an extension and is relative to the current file. Remote +specifiers are also allowed. + +#### X-TypeScript-Types custom header + +If you are hosting modules which you want to be consumed by Deno, and you want +to inform Deno the location of the type definitions, you can use a custom HTTP +header of `X-TypeScript-Types` to inform Deno of the location of that file. + +The header works in the same way as the triple-slash reference mentioned above, +it just means that the content of the JavaScript file itself does not need to be +modified, and the location of the type definitions can be determined by the +server itself. **Not all type definitions are supported.** @@ -592,11 +637,12 @@ include `node`, expecting to resolve to some path like `./node_modules/@types/node/index.d.ts`. Since this depends on non-relative "magical" resolution, Deno cannot resolve this. -**Why not use the triple-slash type reference?** +**Why not use the triple-slash type reference in TypeScript files?** The TypeScript compiler supports triple-slash directives, including a type reference directive. If Deno used this, it would interfere with the behavior of -the TypeScript compiler. +the TypeScript compiler. Deno only looks for the directive in JavaScript (and +JSX) files. ### Testing if current file is the main program diff --git a/tools/http_server.py b/tools/http_server.py index 3152a8982c..9b929c15e5 100755 --- a/tools/http_server.py +++ b/tools/http_server.py @@ -56,17 +56,44 @@ class ContentTypeHandler(QuietSimpleHTTPRequestHandler): if_not_match = self.headers.getheader('if-none-match') if if_not_match == "33a64df551425fcc55e": self.send_response(304, 'Not Modified') - self.send_header('Content-type', 'application/javascript') + self.send_header('Content-type', 'application/typescript') self.send_header('ETag', '33a64df551425fcc55e') self.end_headers() else: self.send_response(200, 'OK') - self.send_header('Content-type', 'application/javascript') + self.send_header('Content-type', 'application/typescript') self.send_header('ETag', '33a64df551425fcc55e') self.end_headers() self.wfile.write(bytes("console.log('etag')")) return + if "xTypeScriptTypes.js" in self.path: + self.protocol_version = "HTTP/1.1" + self.send_response(200, 'OK') + self.send_header('Content-type', 'application/javascript') + self.send_header('X-TypeScript-Types', './xTypeScriptTypes.d.ts') + self.end_headers() + self.wfile.write(bytes("export const foo = 'foo';")) + return + + if "xTypeScriptTypes.d.ts" in self.path: + self.protocol_version = "HTTP/1.1" + self.send_response(200, 'OK') + self.send_header('Content-type', 'application/typescript') + self.end_headers() + self.wfile.write(bytes("export const foo: 'foo';")) + return + + if "referenceTypes.js" in self.path: + self.protocol_version = "HTTP/1.1" + self.send_response(200, 'OK') + self.send_header('Content-type', 'application/javascript') + self.end_headers() + self.wfile.write( + bytes('/// \r\n' + 'export const foo = "foo";\r\n')) + return + if "multipart_form_data.txt" in self.path: self.protocol_version = 'HTTP/1.1' self.send_response(200, 'OK')