Improve support of type definitions (#3755)

This commit is contained in:
Kitson Kelly 2020-01-27 05:59:41 +11:00 committed by Ryan Dahl
parent ec44be0760
commit f604becaba
13 changed files with 430 additions and 26 deletions

View file

@ -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![

View file

@ -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);
}
}

View file

@ -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!(),
});

View file

@ -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.

View file

@ -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
View file

@ -0,0 +1 @@
export const foo: "foo";

View file

@ -0,0 +1,3 @@
/// <reference types="./type_reference.d.ts" />
export const foo = "foo";

View file

@ -0,0 +1,3 @@
import * as foo from "http://127.0.0.1:4545/xTypeScriptTypes.js";
console.log(foo.foo);

View file

@ -0,0 +1,3 @@
[WILDCARD]
DEBUG TS - compiler::host.getSourceFile http://127.0.0.1:4545/xTypeScriptTypes.d.ts
[WILDCARD]

View file

@ -0,0 +1,3 @@
import * as foo from "./subdir/type_reference.js";
console.log(foo.foo);

View file

@ -0,0 +1,3 @@
[WILDCARD]
DEBUG TS - compiler::host.getSourceFile file:[WILDCARD]cli/tests/subdir/type_reference.d.ts
[WILDCARD]

View file

@ -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

View file

@ -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')