mirror of
https://github.com/denoland/deno.git
synced 2025-07-24 05:35:33 +00:00
fix: redirects handling in module analysis (#5726)
This commit fixes a bug introduced in #5029 that caused bad handling of redirects during module analysis. Also ensured that duplicate modules are not downloaded.
This commit is contained in:
parent
ee71099492
commit
f9e45114b9
6 changed files with 90 additions and 13 deletions
|
@ -585,7 +585,6 @@ function buildSourceFileCache(
|
||||||
sourceFileMap: Record<string, SourceFileMapEntry>
|
sourceFileMap: Record<string, SourceFileMapEntry>
|
||||||
): void {
|
): void {
|
||||||
for (const entry of Object.values(sourceFileMap)) {
|
for (const entry of Object.values(sourceFileMap)) {
|
||||||
assert(entry.sourceCode.length > 0);
|
|
||||||
SourceFile.addToCache({
|
SourceFile.addToCache({
|
||||||
url: entry.url,
|
url: entry.url,
|
||||||
filename: entry.url,
|
filename: entry.url,
|
||||||
|
@ -596,7 +595,15 @@ function buildSourceFileCache(
|
||||||
for (const importDesc of entry.imports) {
|
for (const importDesc of entry.imports) {
|
||||||
let mappedUrl = importDesc.resolvedSpecifier;
|
let mappedUrl = importDesc.resolvedSpecifier;
|
||||||
const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
|
const importedFile = sourceFileMap[importDesc.resolvedSpecifier];
|
||||||
|
// IMPORTANT: due to HTTP redirects we might end up in situation
|
||||||
|
// where URL points to a file with completely different URL.
|
||||||
|
// In that case we take value of `redirect` field and cache
|
||||||
|
// resolved specifier pointing to the value of the redirect.
|
||||||
|
// It's not very elegant solution and should be rethinked.
|
||||||
assert(importedFile);
|
assert(importedFile);
|
||||||
|
if (importedFile.redirect) {
|
||||||
|
mappedUrl = importedFile.redirect;
|
||||||
|
}
|
||||||
const isJsOrJsx =
|
const isJsOrJsx =
|
||||||
importedFile.mediaType === MediaType.JavaScript ||
|
importedFile.mediaType === MediaType.JavaScript ||
|
||||||
importedFile.mediaType === MediaType.JSX;
|
importedFile.mediaType === MediaType.JSX;
|
||||||
|
@ -1032,6 +1039,7 @@ interface SourceFileMapEntry {
|
||||||
url: string;
|
url: string;
|
||||||
sourceCode: string;
|
sourceCode: string;
|
||||||
mediaType: MediaType;
|
mediaType: MediaType;
|
||||||
|
redirect?: string;
|
||||||
imports: ImportDescriptor[];
|
imports: ImportDescriptor[];
|
||||||
referencedFiles: ReferenceDescriptor[];
|
referencedFiles: ReferenceDescriptor[];
|
||||||
libDirectives: ReferenceDescriptor[];
|
libDirectives: ReferenceDescriptor[];
|
||||||
|
|
|
@ -19,6 +19,7 @@ use futures::FutureExt;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::hash::BuildHasher;
|
use std::hash::BuildHasher;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -76,6 +77,7 @@ pub struct ReferenceDescriptor {
|
||||||
pub struct ModuleGraphFile {
|
pub struct ModuleGraphFile {
|
||||||
pub specifier: String,
|
pub specifier: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
pub redirect: Option<String>,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub imports: Vec<ImportDescriptor>,
|
pub imports: Vec<ImportDescriptor>,
|
||||||
pub referenced_files: Vec<ReferenceDescriptor>,
|
pub referenced_files: Vec<ReferenceDescriptor>,
|
||||||
|
@ -87,13 +89,14 @@ pub struct ModuleGraphFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
type SourceFileFuture =
|
type SourceFileFuture =
|
||||||
Pin<Box<dyn Future<Output = Result<SourceFile, ErrBox>>>>;
|
Pin<Box<dyn Future<Output = Result<(ModuleSpecifier, SourceFile), ErrBox>>>>;
|
||||||
|
|
||||||
pub struct ModuleGraphLoader {
|
pub struct ModuleGraphLoader {
|
||||||
permissions: Permissions,
|
permissions: Permissions,
|
||||||
file_fetcher: SourceFileFetcher,
|
file_fetcher: SourceFileFetcher,
|
||||||
maybe_import_map: Option<ImportMap>,
|
maybe_import_map: Option<ImportMap>,
|
||||||
pending_downloads: FuturesUnordered<SourceFileFuture>,
|
pending_downloads: FuturesUnordered<SourceFileFuture>,
|
||||||
|
has_downloaded: HashSet<ModuleSpecifier>,
|
||||||
pub graph: ModuleGraph,
|
pub graph: ModuleGraph,
|
||||||
is_dyn_import: bool,
|
is_dyn_import: bool,
|
||||||
analyze_dynamic_imports: bool,
|
analyze_dynamic_imports: bool,
|
||||||
|
@ -112,6 +115,7 @@ impl ModuleGraphLoader {
|
||||||
permissions,
|
permissions,
|
||||||
maybe_import_map,
|
maybe_import_map,
|
||||||
pending_downloads: FuturesUnordered::new(),
|
pending_downloads: FuturesUnordered::new(),
|
||||||
|
has_downloaded: HashSet::new(),
|
||||||
graph: ModuleGraph(HashMap::new()),
|
graph: ModuleGraph(HashMap::new()),
|
||||||
is_dyn_import,
|
is_dyn_import,
|
||||||
analyze_dynamic_imports,
|
analyze_dynamic_imports,
|
||||||
|
@ -131,8 +135,9 @@ impl ModuleGraphLoader {
|
||||||
self.download_module(specifier.clone(), None)?;
|
self.download_module(specifier.clone(), None)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let source_file = self.pending_downloads.next().await.unwrap()?;
|
let (specifier, source_file) =
|
||||||
self.visit_module(&source_file.url.clone().into(), source_file)?;
|
self.pending_downloads.next().await.unwrap()?;
|
||||||
|
self.visit_module(&specifier, source_file)?;
|
||||||
if self.pending_downloads.is_empty() {
|
if self.pending_downloads.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -260,6 +265,7 @@ impl ModuleGraphLoader {
|
||||||
ModuleGraphFile {
|
ModuleGraphFile {
|
||||||
specifier: specifier.to_string(),
|
specifier: specifier.to_string(),
|
||||||
url: specifier.to_string(),
|
url: specifier.to_string(),
|
||||||
|
redirect: None,
|
||||||
media_type: map_file_extension(&PathBuf::from(specifier.clone()))
|
media_type: map_file_extension(&PathBuf::from(specifier.clone()))
|
||||||
as i32,
|
as i32,
|
||||||
filename: specifier,
|
filename: specifier,
|
||||||
|
@ -281,7 +287,7 @@ impl ModuleGraphLoader {
|
||||||
module_specifier: ModuleSpecifier,
|
module_specifier: ModuleSpecifier,
|
||||||
maybe_referrer: Option<ModuleSpecifier>,
|
maybe_referrer: Option<ModuleSpecifier>,
|
||||||
) -> Result<(), ErrBox> {
|
) -> Result<(), ErrBox> {
|
||||||
if self.graph.0.contains_key(&module_specifier.to_string()) {
|
if self.has_downloaded.contains(&module_specifier) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +325,7 @@ impl ModuleGraphLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.has_downloaded.insert(module_specifier.clone());
|
||||||
let spec = module_specifier;
|
let spec = module_specifier;
|
||||||
let file_fetcher = self.file_fetcher.clone();
|
let file_fetcher = self.file_fetcher.clone();
|
||||||
let perms = self.permissions.clone();
|
let perms = self.permissions.clone();
|
||||||
|
@ -328,13 +335,7 @@ impl ModuleGraphLoader {
|
||||||
let source_file = file_fetcher
|
let source_file = file_fetcher
|
||||||
.fetch_source_file(&spec_, maybe_referrer, perms)
|
.fetch_source_file(&spec_, maybe_referrer, perms)
|
||||||
.await?;
|
.await?;
|
||||||
// FIXME(bartlomieju):
|
Ok((spec_.clone(), source_file))
|
||||||
// because of redirects we may end up with wrong URL,
|
|
||||||
// substitute with original one
|
|
||||||
Ok(SourceFile {
|
|
||||||
url: spec_.as_url().to_owned(),
|
|
||||||
..source_file
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
.boxed_local();
|
.boxed_local();
|
||||||
|
|
||||||
|
@ -353,6 +354,33 @@ impl ModuleGraphLoader {
|
||||||
let mut types_directives = vec![];
|
let mut types_directives = vec![];
|
||||||
let mut type_headers = vec![];
|
let mut type_headers = vec![];
|
||||||
|
|
||||||
|
// IMPORTANT: source_file.url might be different than requested
|
||||||
|
// module_specifier because of HTTP redirects. In such
|
||||||
|
// situation we add an "empty" ModuleGraphFile with 'redirect'
|
||||||
|
// field set that will be later used in TS worker when building
|
||||||
|
// map of available source file. It will perform substitution
|
||||||
|
// for proper URL point to redirect target.
|
||||||
|
if module_specifier.as_url() != &source_file.url {
|
||||||
|
// TODO(bartlomieju): refactor, this is a band-aid
|
||||||
|
self.graph.0.insert(
|
||||||
|
module_specifier.to_string(),
|
||||||
|
ModuleGraphFile {
|
||||||
|
specifier: module_specifier.to_string(),
|
||||||
|
url: module_specifier.to_string(),
|
||||||
|
redirect: Some(source_file.url.to_string()),
|
||||||
|
filename: source_file.filename.to_str().unwrap().to_string(),
|
||||||
|
media_type: source_file.media_type as i32,
|
||||||
|
source_code: "".to_string(),
|
||||||
|
imports: vec![],
|
||||||
|
referenced_files: vec![],
|
||||||
|
lib_directives: vec![],
|
||||||
|
types_directives: vec![],
|
||||||
|
type_headers: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_specifier = ModuleSpecifier::from(source_file.url.clone());
|
||||||
let source_code = String::from_utf8(source_file.source_code)?;
|
let source_code = String::from_utf8(source_file.source_code)?;
|
||||||
|
|
||||||
if source_file.media_type == MediaType::JavaScript
|
if source_file.media_type == MediaType::JavaScript
|
||||||
|
@ -470,7 +498,8 @@ impl ModuleGraphLoader {
|
||||||
module_specifier.to_string(),
|
module_specifier.to_string(),
|
||||||
ModuleGraphFile {
|
ModuleGraphFile {
|
||||||
specifier: module_specifier.to_string(),
|
specifier: module_specifier.to_string(),
|
||||||
url: source_file.url.to_string(),
|
url: module_specifier.to_string(),
|
||||||
|
redirect: None,
|
||||||
filename: source_file.filename.to_str().unwrap().to_string(),
|
filename: source_file.filename.to_str().unwrap().to_string(),
|
||||||
media_type: source_file.media_type as i32,
|
media_type: source_file.media_type as i32,
|
||||||
source_code,
|
source_code,
|
||||||
|
|
|
@ -1565,6 +1565,12 @@ itest!(type_directives_js_main {
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itest!(type_directives_redirect {
|
||||||
|
args: "run --reload type_directives_redirect.ts",
|
||||||
|
output: "type_directives_redirect.ts.out",
|
||||||
|
http_server: true,
|
||||||
|
});
|
||||||
|
|
||||||
itest!(types {
|
itest!(types {
|
||||||
args: "types",
|
args: "types",
|
||||||
output: "types.out",
|
output: "types.out",
|
||||||
|
|
1
cli/tests/type_directives_redirect.ts
Normal file
1
cli/tests/type_directives_redirect.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import "http://localhost:4545/type_directives_redirect.js";
|
5
cli/tests/type_directives_redirect.ts.out
Normal file
5
cli/tests/type_directives_redirect.ts.out
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Download [WILDCARD]type_directives_redirect.js
|
||||||
|
Download [WILDCARD]xTypeScriptTypesRedirect.d.ts
|
||||||
|
Download [WILDCARD]xTypeScriptTypesRedirect.d.ts
|
||||||
|
Download [WILDCARD]xTypeScriptTypesRedirected.d.ts
|
||||||
|
Compile [WILDCARD]type_directives_redirect.ts
|
|
@ -122,6 +122,34 @@ class ContentTypeHandler(QuietSimpleHTTPRequestHandler):
|
||||||
self.wfile.write(bytes("export const foo = 'foo';"))
|
self.wfile.write(bytes("export const foo = 'foo';"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if "type_directives_redirect.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',
|
||||||
|
'http://localhost:4547/xTypeScriptTypesRedirect.d.ts')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(bytes("export const foo = 'foo';"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if "xTypeScriptTypesRedirect.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("import './xTypeScriptTypesRedirected.d.ts';"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if "xTypeScriptTypesRedirected.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 "xTypeScriptTypes.d.ts" in self.path:
|
if "xTypeScriptTypes.d.ts" in self.path:
|
||||||
self.protocol_version = "HTTP/1.1"
|
self.protocol_version = "HTTP/1.1"
|
||||||
self.send_response(200, 'OK')
|
self.send_response(200, 'OK')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue