Skip to content

Commit 7e5950a

Browse files
authored
fix: alias value should try fragment as path (#172)
1 parent ac34239 commit 7e5950a

File tree

5 files changed

+69
-37
lines changed

5 files changed

+69
-37
lines changed

fixtures/enhanced_resolve/test/fixtures/#/a.js

Whitespace-only changes.

fixtures/enhanced_resolve/test/fixtures/browser-module/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"./xyz.js": "./lib/xyz.js",
1616
"./lib/non-existent.js": "./lib/non-existent.js",
1717
".": false,
18-
"./number": 1
18+
"./number": 1,
19+
"./foo": "./lib/replaced.js?query"
1920
},
2021
"innerBrowser1": {
2122
"field": {

src/lib.rs

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
225225
ctx: &mut Ctx,
226226
) -> Result<Resolution, ResolveError> {
227227
ctx.with_fully_specified(self.options.fully_specified);
228-
let specifier = Specifier::parse(specifier).map_err(ResolveError::Specifier)?;
229-
ctx.with_query_fragment(specifier.query, specifier.fragment);
230228
let cached_path = self.cache.value(path);
231-
let cached_path = self.require(&cached_path, specifier.path(), ctx).or_else(|err| {
232-
if err.is_ignore() {
233-
return Err(err);
234-
}
235-
// enhanced-resolve: try fallback
236-
self.load_alias(&cached_path, specifier.path(), &self.options.fallback, ctx)
237-
.and_then(|value| value.ok_or(err))
238-
})?;
229+
let cached_path = self.require(&cached_path, specifier, ctx)?;
239230
let path = self.load_realpath(&cached_path)?;
240231
// enhanced-resolve: restrictions
241232
self.check_restrictions(&path)?;
@@ -266,11 +257,21 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
266257
) -> Result<CachedPath, ResolveError> {
267258
ctx.test_for_infinite_recursion()?;
268259

269-
// enhanced-resolve: try fragment as path
270-
if let Some(path) = self.try_fragment_as_path(cached_path, specifier, ctx) {
260+
// enhanced-resolve: parse
261+
let (parsed, try_fragment_as_path) = self.load_parse(cached_path, specifier, ctx)?;
262+
if let Some(path) = try_fragment_as_path {
271263
return Ok(path);
272264
}
273265

266+
self.require_without_parse(cached_path, parsed.path(), ctx)
267+
}
268+
269+
fn require_without_parse(
270+
&self,
271+
cached_path: &CachedPath,
272+
specifier: &str,
273+
ctx: &mut Ctx,
274+
) -> Result<CachedPath, ResolveError> {
274275
// tsconfig-paths
275276
if let Some(path) = self.load_tsconfig_paths(cached_path, specifier, &mut Ctx::default())? {
276277
return Ok(path);
@@ -281,7 +282,7 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
281282
return Ok(path);
282283
}
283284

284-
match Path::new(specifier).components().next() {
285+
let result = match Path::new(specifier).components().next() {
285286
// 3. If X begins with './' or '/' or '../'
286287
Some(Component::RootDir | Component::Prefix(_)) => {
287288
self.require_absolute(cached_path, specifier, ctx)
@@ -305,7 +306,16 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
305306
// Set resolved the result of PACKAGE_RESOLVE(specifier, parentURL).
306307
self.require_bare(cached_path, specifier, ctx)
307308
}
308-
}
309+
};
310+
311+
result.or_else(|err| {
312+
if err.is_ignore() {
313+
return Err(err);
314+
}
315+
// enhanced-resolve: try fallback
316+
self.load_alias(cached_path, specifier, &self.options.fallback, ctx)
317+
.and_then(|value| value.ok_or(err))
318+
})
309319
}
310320

311321
// PACKAGE_RESOLVE(packageSpecifier, parentURL)
@@ -409,29 +419,34 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
409419
self.load_package_self_or_node_modules(cached_path, specifier, ctx)
410420
}
411421

412-
/// Try fragment as part of the path
422+
/// enhanced-resolve: ParsePlugin.
413423
///
414424
/// It's allowed to escape # as \0# to avoid parsing it as fragment.
415425
/// enhanced-resolve will try to resolve requests containing `#` as path and as fragment,
416426
/// so it will automatically figure out if `./some#thing` means `.../some.js#thing` or `.../some#thing.js`.
417427
/// When a # is resolved as path it will be escaped in the result. Here: `.../some\0#thing.js`.
418428
///
419429
/// <https://github.com/webpack/enhanced-resolve#escaping>
420-
fn try_fragment_as_path(
430+
fn load_parse<'s>(
421431
&self,
422432
cached_path: &CachedPath,
423-
specifier: &str,
433+
specifier: &'s str,
424434
ctx: &mut Ctx,
425-
) -> Option<CachedPath> {
435+
) -> Result<(Specifier<'s>, Option<CachedPath>), ResolveError> {
436+
let parsed = Specifier::parse(specifier).map_err(ResolveError::Specifier)?;
437+
ctx.with_query_fragment(parsed.query, parsed.fragment);
438+
439+
// There is an edge-case where a request with # can be a path or a fragment -> try both
426440
if ctx.fragment.is_some() && ctx.query.is_none() {
441+
let specifier = parsed.path();
427442
let fragment = ctx.fragment.take().unwrap();
428443
let path = format!("{specifier}{fragment}");
429-
if let Ok(path) = self.require(cached_path, &path, ctx) {
430-
return Some(path);
444+
if let Ok(path) = self.require_without_parse(cached_path, &path, ctx) {
445+
return Ok((parsed, Some(path)));
431446
}
432447
ctx.fragment.replace(fragment);
433448
}
434-
None
449+
Ok((parsed, None))
435450
}
436451

437452
fn load_package_self_or_node_modules(
@@ -850,12 +865,10 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
850865
}
851866
return Err(ResolveError::Recursion);
852867
}
853-
let specifier = Specifier::parse(new_specifier).map_err(ResolveError::Specifier)?;
854-
ctx.with_query_fragment(specifier.query, specifier.fragment);
855-
ctx.with_resolving_alias(specifier.path().to_string());
868+
ctx.with_resolving_alias(new_specifier.to_string());
856869
ctx.with_fully_specified(false);
857870
let cached_path = self.cache.value(package_json.directory());
858-
self.require(&cached_path, specifier.path(), ctx).map(Some)
871+
self.require(&cached_path, new_specifier, ctx).map(Some)
859872
}
860873

861874
/// enhanced-resolve: AliasPlugin for [ResolveOptions::alias] and [ResolveOptions::fallback].
@@ -886,23 +899,16 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
886899
for r in specifiers {
887900
match r {
888901
AliasValue::Path(alias_value) => {
889-
let new_specifier =
890-
Specifier::parse(alias_value).map_err(ResolveError::Specifier)?;
891-
// Resolve path without query and fragment
892-
let old_query = ctx.query.clone();
893-
let old_fragment = ctx.fragment.clone();
894-
ctx.with_query_fragment(new_specifier.query, new_specifier.fragment);
895902
if let Some(path) = self.load_alias_value(
896903
cached_path,
897904
alias_key,
898-
new_specifier.path(), // pass in parsed alias value
905+
alias_value,
899906
specifier,
900907
ctx,
901908
&mut should_stop,
902909
)? {
903910
return Ok(Some(path));
904911
}
905-
ctx.with_query_fragment(old_query.as_deref(), old_fragment.as_deref());
906912
}
907913
AliasValue::Ignore => {
908914
let path = cached_path.path().normalize_with(alias_key);
@@ -1175,10 +1181,8 @@ impl<Fs: FileSystem> ResolverGeneric<Fs> {
11751181
}
11761182
}
11771183
let subpath = format!(".{subpath}");
1178-
let specifier = Specifier::parse(&subpath).map_err(ResolveError::Specifier)?;
11791184
ctx.with_fully_specified(false);
1180-
ctx.with_query_fragment(specifier.query, specifier.fragment);
1181-
return self.require(&cached_path, specifier.path(), ctx).map(Some);
1185+
return self.require(&cached_path, &subpath, ctx).map(Some);
11821186
}
11831187
}
11841188
}

src/tests/alias.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,17 @@ fn alias_fragment() {
264264
assert_eq!(resolved_path, Ok(expected), "{comment} {request}");
265265
}
266266
}
267+
268+
#[test]
269+
fn alias_try_fragment_as_path() {
270+
let f = super::fixture();
271+
let resolver = Resolver::new(ResolveOptions {
272+
alias: vec![(
273+
"#".to_string(),
274+
vec![AliasValue::Path(f.join("#").to_string_lossy().to_string())],
275+
)],
276+
..ResolveOptions::default()
277+
});
278+
let resolution = resolver.resolve(&f, "#/a").map(|r| r.full_path());
279+
assert_eq!(resolution, Ok(f.join("#").join("a.js")));
280+
}

src/tests/browser_field.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,16 @@ fn recursive() {
174174
assert_eq!(resolved_path, Err(ResolveError::Recursion), "{comment} {path:?} {request}");
175175
}
176176
}
177+
178+
#[test]
179+
fn with_query() {
180+
let f = super::fixture().join("browser-module");
181+
182+
let resolver = Resolver::new(ResolveOptions {
183+
alias_fields: vec![vec!["browser".into()]],
184+
..ResolveOptions::default()
185+
});
186+
187+
let resolved_path = resolver.resolve(&f, "./foo").map(|r| r.full_path());
188+
assert_eq!(resolved_path, Ok(f.join("lib").join("browser.js?query")));
189+
}

0 commit comments

Comments
 (0)