Skip to content

Commit 1abd70b

Browse files
committed
Merge remote-tracking branch 'upstream/main' into fix_types_package_specifier
# Conflicts: # rs-lib/src/lib.rs
2 parents 1b0470c + 8e42209 commit 1abd70b

10 files changed

Lines changed: 621 additions & 15 deletions

File tree

lib/package_json.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Deno.test("single entrypoint", () => {
3232
}],
3333
},
3434
warnings: [],
35+
typesDependencies: [],
3536
},
3637
entryPoints: [{
3738
name: ".",
@@ -252,6 +253,7 @@ Deno.test("exports have default last", () => {
252253
dependencies: [],
253254
},
254255
warnings: [],
256+
typesDependencies: [],
255257
},
256258
entryPoints: [
257259
{
@@ -299,6 +301,7 @@ Deno.test("multiple entrypoints", () => {
299301
dependencies: [],
300302
},
301303
warnings: [],
304+
typesDependencies: [],
302305
},
303306
entryPoints: [{
304307
name: ".",
@@ -375,6 +378,7 @@ Deno.test("binary entrypoints", () => {
375378
dependencies: [],
376379
},
377380
warnings: [],
381+
typesDependencies: [],
378382
},
379383
entryPoints: [{
380384
name: ".",
@@ -457,6 +461,7 @@ Deno.test("peer dependencies", () => {
457461
}],
458462
},
459463
warnings: [],
464+
typesDependencies: [],
460465
},
461466
entryPoints: [{
462467
name: ".",
@@ -520,3 +525,58 @@ Deno.test("peer dependencies", () => {
520525
),
521526
);
522527
});
528+
529+
Deno.test("types dependencies", () => {
530+
const props: GetPackageJsonOptions = {
531+
transformOutput: {
532+
main: {
533+
files: [],
534+
dependencies: [{
535+
name: "svg-path-parser",
536+
version: "1.1.0",
537+
}, {
538+
name: "other",
539+
version: "^1.0.0",
540+
}],
541+
entryPoints: ["mod.ts"],
542+
},
543+
test: {
544+
entryPoints: [],
545+
files: [],
546+
dependencies: [],
547+
},
548+
warnings: [],
549+
typesDependencies: [{
550+
name: "@types/svg-path-parser",
551+
version: "~1.1.6",
552+
}, {
553+
// a types package that's already a dependency is not duplicated
554+
name: "other",
555+
version: "^1.0.0",
556+
}],
557+
},
558+
entryPoints: [{
559+
name: ".",
560+
path: "./mod.ts",
561+
}],
562+
package: {
563+
name: "package",
564+
version: "0.1.0",
565+
},
566+
testEnabled: false,
567+
includeEsModule: true,
568+
includeScriptModule: true,
569+
includeDeclarations: true,
570+
includeTsLib: false,
571+
shims: {},
572+
};
573+
574+
const packageJson = getPackageJson(props);
575+
assertEquals(packageJson.dependencies, {
576+
"svg-path-parser": "1.1.0",
577+
"other": "^1.0.0",
578+
});
579+
assertEquals(packageJson.devDependencies, {
580+
"@types/svg-path-parser": "~1.1.6",
581+
});
582+
});

lib/package_json.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ export function getPackageJson({
7979
})
8080
: {};
8181
const devDependencies = {
82+
// packages that provide the type declarations of a dependency
83+
// (ex. an `@types/` package specified by an `X-TypeScript-Types` header)
84+
...Object.fromEntries(
85+
transformOutput.typesDependencies
86+
.filter((d) =>
87+
!Object.keys(dependencies).includes(d.name) &&
88+
!Object.keys(peerDependencies).includes(d.name)
89+
)
90+
.map((d) => [d.name, d.version]),
91+
),
8292
...(shouldIncludeTypesNode()
8393
? {
8494
"@types/node": "^20.9.0",

rs-lib/src/declaration_file_resolution.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use deno_graph::Module;
1010
use deno_graph::Resolution;
1111

1212
use crate::graph::ModuleGraph;
13+
use crate::loader::get_all_specifier_mappers;
14+
use crate::loader::get_types_package_for_specifier;
1315
use crate::PackageMappedSpecifier;
1416

1517
#[derive(Debug)]
@@ -27,29 +29,50 @@ pub struct TypesDependency {
2729
pub referrer: ModuleSpecifier,
2830
}
2931

32+
pub struct DeclarationFileResolutions {
33+
pub mappings: BTreeMap<ModuleSpecifier, DeclarationFileResolution>,
34+
/// Packages that provide the declaration files of a mapped package
35+
/// (ex. an `@types/` package served by a cdn).
36+
pub types_packages: BTreeMap<String, PackageMappedSpecifier>,
37+
/// Declaration files that a types package provides, which don't
38+
/// need to be included in the output.
39+
pub types_package_files: HashSet<ModuleSpecifier>,
40+
}
41+
3042
pub fn resolve_declaration_file_mappings(
3143
module_graph: &ModuleGraph,
3244
modules: &[&Module],
3345
mapped_specifiers: &BTreeMap<ModuleSpecifier, PackageMappedSpecifier>,
34-
) -> Result<BTreeMap<ModuleSpecifier, DeclarationFileResolution>> {
46+
) -> Result<DeclarationFileResolutions> {
3547
let mut type_dependencies = BTreeMap::new();
3648

3749
for module in modules.iter().filter_map(|m| m.js()) {
3850
fill_types_for_module(module_graph, module, &mut type_dependencies)?;
3951
}
4052

4153
// get the resolved type dependencies
54+
let mappers = get_all_specifier_mappers();
4255
let mut mappings = BTreeMap::new();
56+
let mut types_packages = BTreeMap::new();
57+
let mut types_package_files = HashSet::new();
4358
for (code_specifier, deps) in type_dependencies.into_iter() {
44-
// if this type_dependency is mapped, then pass it.
45-
if mapped_specifiers.contains_key(&code_specifier) {
46-
continue;
47-
}
48-
4959
let deps = deps.into_iter().collect::<Vec<_>>();
5060
let selected_dep =
5161
select_best_types_dep(module_graph, &code_specifier, &deps);
5262

63+
// when the code is mapped to a package, the declaration file it
64+
// specifies may be provided by a types package that needs to go in
65+
// the package.json instead of being included in the output
66+
if mapped_specifiers.contains_key(&code_specifier) {
67+
if let Some(types_package) =
68+
get_types_package_for_specifier(&mappers, &selected_dep.specifier)
69+
{
70+
types_packages.insert(types_package.name.clone(), types_package);
71+
types_package_files.insert(selected_dep.specifier);
72+
}
73+
continue;
74+
}
75+
5376
// get the declaration file specifiers that weren't used
5477
let mut ignored = deps
5578
.into_iter()
@@ -66,7 +89,11 @@ pub fn resolve_declaration_file_mappings(
6689
);
6790
}
6891

69-
Ok(mappings)
92+
Ok(DeclarationFileResolutions {
93+
mappings,
94+
types_packages,
95+
types_package_files,
96+
})
7097
}
7198

7299
/// This resolution process works as follows:

rs-lib/src/lib.rs

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use analyze::get_top_level_decls;
1515
use anyhow::Context;
1616
use anyhow::Result;
1717

18+
use crate::loader::get_all_specifier_mappers;
19+
use crate::loader::get_types_package_for_specifier;
1820
use analyze::get_ignore_line_indexes;
1921
use anyhow::bail;
2022
use deno_ast::apply_text_changes;
@@ -107,6 +109,9 @@ pub struct TransformOutput {
107109
/// This is `None` when no config file was found, when one was explicitly
108110
/// provided, or when auto-discovery is disabled.
109111
pub discovered_config_file: Option<PathBuf>,
112+
/// Packages that provide the type declarations of a mapped dependency
113+
/// (ex. an `@types/` package specified by an `X-TypeScript-Types` header).
114+
pub types_dependencies: Vec<Dependency>,
110115
}
111116

112117
#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
@@ -463,8 +468,9 @@ pub async fn transform(
463468
.await?
464469
.cloned();
465470

471+
let file_fetcher = Rc::new(file_fetcher);
466472
let loader = Rc::new(DenoGraphLoader::new(
467-
Rc::new(file_fetcher),
473+
file_fetcher.clone(),
468474
resolver_factory
469475
.workspace_factory()
470476
.global_http_cache()?
@@ -521,6 +527,8 @@ pub async fn transform(
521527
.collect();
522528

523529
let mut warnings = get_declaration_warnings(&specifiers);
530+
let types_dependencies =
531+
get_types_dependencies(&specifiers, &file_fetcher, &mut warnings).await;
524532
let mut main_env_context = EnvironmentContext {
525533
environment: TransformOutputEnvironment {
526534
entry_points: options
@@ -706,6 +714,7 @@ pub async fn transform(
706714
test: test_env_context.environment,
707715
warnings,
708716
discovered_config_file,
717+
types_dependencies,
709718
})
710719
}
711720

@@ -991,6 +1000,95 @@ fn to_module_name(name: &str) -> String {
9911000
}
9921001
}
9931002

1003+
/// Gets the packages that provide the type declarations of the mapped
1004+
/// dependencies, which are added to the dev dependencies.
1005+
///
1006+
/// A mapped module isn't part of the module graph, so the `X-TypeScript-Types`
1007+
/// header of each is resolved here.
1008+
async fn get_types_dependencies<TSys: WorkspaceFactorySys>(
1009+
specifiers: &Specifiers,
1010+
file_fetcher: &PermissionedFileFetcher<
1011+
NullBlobStore,
1012+
TSys,
1013+
impl deno_cache_dir::file_fetcher::HttpClient,
1014+
>,
1015+
warnings: &mut Vec<String>,
1016+
) -> Vec<Dependency> {
1017+
let mappers = get_all_specifier_mappers();
1018+
let mut types_packages = specifiers.types_packages.clone();
1019+
let header_packages = futures::future::join_all(
1020+
specifiers
1021+
.main
1022+
.mapped
1023+
.keys()
1024+
.chain(specifiers.test.mapped.keys())
1025+
// only a module mapped by one of the cdn mappers may have a types
1026+
// header, so don't fetch the modules the user mapped themselves
1027+
.filter(|s| mappers.iter().any(|m| m.map(s).is_some()))
1028+
.map(|specifier| {
1029+
let mappers = &mappers;
1030+
async move {
1031+
match get_types_header_specifier(file_fetcher, specifier).await {
1032+
Ok(types_specifier) => Ok(
1033+
types_specifier
1034+
.and_then(|s| get_types_package_for_specifier(mappers, &s)),
1035+
),
1036+
Err(err) => Err(format!(
1037+
"Failed getting the type declarations of {}. {:#}",
1038+
specifier, err
1039+
)),
1040+
}
1041+
}
1042+
}),
1043+
)
1044+
.await;
1045+
for result in header_packages {
1046+
match result {
1047+
Ok(Some(package)) => {
1048+
// a declaration file specified in the code takes precedence
1049+
types_packages
1050+
.entry(package.name.clone())
1051+
.or_insert(package);
1052+
}
1053+
Ok(None) => {}
1054+
Err(warning) => warnings.push(warning),
1055+
}
1056+
}
1057+
1058+
types_packages
1059+
.into_values()
1060+
.filter_map(|p| {
1061+
Some(Dependency {
1062+
name: p.name,
1063+
version: p.version?,
1064+
peer_dependency: false,
1065+
})
1066+
})
1067+
.collect()
1068+
}
1069+
1070+
/// Gets the specifier of the declaration file in a module's
1071+
/// `X-TypeScript-Types` header.
1072+
async fn get_types_header_specifier<TSys: WorkspaceFactorySys>(
1073+
file_fetcher: &PermissionedFileFetcher<
1074+
NullBlobStore,
1075+
TSys,
1076+
impl deno_cache_dir::file_fetcher::HttpClient,
1077+
>,
1078+
specifier: &ModuleSpecifier,
1079+
) -> Result<Option<ModuleSpecifier>> {
1080+
let mut file = file_fetcher.fetch_bypass_permissions(specifier).await?;
1081+
let Some(types_header) = file
1082+
.maybe_headers
1083+
.as_mut()
1084+
.and_then(|h| h.remove("x-typescript-types"))
1085+
else {
1086+
return Ok(None);
1087+
};
1088+
// resolve relative to the final url so that redirects are handled
1089+
Ok(file.url.join(&types_header).ok())
1090+
}
1091+
9941092
fn get_dependencies(
9951093
mappings: BTreeMap<ModuleSpecifier, PackageMappedSpecifier>,
9961094
) -> Vec<Dependency> {

0 commit comments

Comments
 (0)