mirror of
https://github.com/denoland/deno.git
synced 2024-12-26 00:59:24 -05:00
Improve support of type definitions (#3755)
This commit is contained in:
parent
ec44be0760
commit
f604becaba
13 changed files with 430 additions and 26 deletions
|
@ -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![
|
||||
|
|
|
@ -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<Url>,
|
||||
pub media_type: msg::MediaType,
|
||||
pub source_code: Vec<u8>,
|
||||
}
|
||||
|
@ -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<String>,
|
||||
redirect_to: Option<String>,
|
||||
etag: Option<String>,
|
||||
x_typescript_types: Option<String>,
|
||||
) -> 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<Url> {
|
||||
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*<reference\s+types\s*=\s*["']([^"']+)["']\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<u8>) -> Vec<u8> {
|
||||
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<String>,
|
||||
/// ETag of the remote source file
|
||||
pub etag: Option<String>,
|
||||
/// X-TypeScript-Types defines the location of a .d.ts file
|
||||
pub x_typescript_types: Option<String>,
|
||||
}
|
||||
|
||||
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#"/// <reference types="./mod.d.ts" />
|
||||
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#"/// <reference types="https://deno.land/mod.d.ts" />
|
||||
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#"/// <reference types="../qat/baz.d.ts" />
|
||||
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#"/// <reference types="./mod.d.ts" />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
pub etag: Option<String>,
|
||||
pub x_typescript_types: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FetchOnceResult {
|
||||
// (code, maybe_content_type, etag)
|
||||
Code(String, Option<String>, Option<String>),
|
||||
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!(),
|
||||
});
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
1
cli/tests/subdir/type_reference.d.ts
vendored
Normal file
1
cli/tests/subdir/type_reference.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
export const foo: "foo";
|
3
cli/tests/subdir/type_reference.js
Normal file
3
cli/tests/subdir/type_reference.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
/// <reference types="./type_reference.d.ts" />
|
||||
|
||||
export const foo = "foo";
|
3
cli/tests/type_directives_01.ts
Normal file
3
cli/tests/type_directives_01.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import * as foo from "http://127.0.0.1:4545/xTypeScriptTypes.js";
|
||||
|
||||
console.log(foo.foo);
|
3
cli/tests/type_directives_01.ts.out
Normal file
3
cli/tests/type_directives_01.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]
|
||||
DEBUG TS - compiler::host.getSourceFile http://127.0.0.1:4545/xTypeScriptTypes.d.ts
|
||||
[WILDCARD]
|
3
cli/tests/type_directives_02.ts
Normal file
3
cli/tests/type_directives_02.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import * as foo from "./subdir/type_reference.js";
|
||||
|
||||
console.log(foo.foo);
|
3
cli/tests/type_directives_02.ts.out
Normal file
3
cli/tests/type_directives_02.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]
|
||||
DEBUG TS - compiler::host.getSourceFile file:[WILDCARD]cli/tests/subdir/type_reference.d.ts
|
||||
[WILDCARD]
|
|
@ -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
|
||||
/// <reference types="./foo.d.ts" />
|
||||
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
|
||||
|
||||
|
|
|
@ -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('/// <reference types="./xTypeScriptTypes.d.ts" />\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')
|
||||
|
|
Loading…
Reference in a new issue