Skip to content

Commit 39c0e3a

Browse files
authored
Merge pull request #255 from webpack/bugfix/no-fragment
resolve folders which look like a fragment
2 parents 0e0f836 + 17cf8fe commit 39c0e3a

File tree

7 files changed

+62
-4
lines changed

7 files changed

+62
-4
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ class MyResolverPlugin {
131131

132132
Plugins are executed in a pipeline, and register which event they should be executed before/after. In the example above, `source` is the name of the event that starts the pipeline, and `target` is what event this plugin should fire, which is what continues the execution of the pipeline. For an example of how these different plugin events create a chain, see `lib/ResolverFactory.js`, in the `//// pipeline ////` section.
133133

134+
## Escaping
135+
136+
It's allowed to escape `#` as `\0#` to avoid parsing it as fragment.
137+
138+
enhanced-resolve will try to resolve requests containing `#` as path and as fragment, so it will automatically figure out if `./some#thing` means `.../some.js#thing` or `.../some#thing.js`. When a `#` is resolved as path it will be escaped in the result. Here: `.../some\0#thing.js`.
139+
134140
## Tests
135141

136142
```javascript

lib/ParsePlugin.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,31 @@ module.exports = class ParsePlugin {
4343
if (parsed.directory)
4444
resolveContext.log("Parsed request is a directory");
4545
}
46+
// There is an edge-case where a request with # can be a path or a fragment -> try both
47+
if (obj.request && !obj.query && obj.fragment) {
48+
const directory = obj.fragment.endsWith("/");
49+
const alternative = {
50+
...obj,
51+
directory,
52+
request:
53+
obj.request +
54+
(obj.directory ? "/" : "") +
55+
(directory ? obj.fragment.slice(0, -1) : obj.fragment),
56+
fragment: ""
57+
};
58+
resolver.doResolve(
59+
target,
60+
alternative,
61+
null,
62+
resolveContext,
63+
(err, result) => {
64+
if (err) return callback(err);
65+
if (result) return callback(null, result);
66+
resolver.doResolve(target, obj, null, resolveContext, callback);
67+
}
68+
);
69+
return;
70+
}
4671
resolver.doResolve(target, obj, null, resolveContext, callback);
4772
});
4873
}

lib/Resolver.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,9 @@ class Resolver {
270270
null,
271271
result.path === false
272272
? false
273-
: `${result.path}${result.query || ""}${result.fragment || ""}`,
273+
: `${result.path.replace(/#/g, "\0#")}${
274+
result.query ? result.query.replace(/#/g, "\0#") : ""
275+
}${result.fragment || ""}`,
274276
result
275277
);
276278
};

lib/util/identifier.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@
55

66
"use strict";
77

8-
const EMPTY = "";
8+
const PATH_QUERY_FRAGMENT_REGEXP = /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
99

1010
/**
1111
* @param {string} identifier identifier
1212
* @returns {[string, string, string]|null} parsed identifier
1313
*/
1414
function parseIdentifier(identifier) {
15-
const match = /^(#?[^?#]*)(\?[^#]*)?(#.*)?$/.exec(identifier);
15+
const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);
1616

1717
if (!match) return null;
1818

19-
return [match[1] || EMPTY, match[2] || EMPTY, match[3] || EMPTY];
19+
return [
20+
match[1].replace(/\0(.)/g, "$1"),
21+
match[2] ? match[2].replace(/\0(.)/g, "$1") : "",
22+
match[3] || ""
23+
];
2024
}
2125

2226
module.exports.parseIdentifier = parseIdentifier;

test/fixtures/no#fragment/#/#.js

Whitespace-only changes.

test/fixtures/no.js

Whitespace-only changes.

test/resolve.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,27 @@ describe("resolve", function () {
239239
path.join(fixtures, "main-field-self2", "index.js")
240240
);
241241

242+
testResolve(
243+
"handle fragment edge case (no fragment)",
244+
fixtures,
245+
"./no#fragment/#/#",
246+
path.join(fixtures, "no\0#fragment/\0#", "\0#.js")
247+
);
248+
249+
testResolve(
250+
"handle fragment edge case (fragment)",
251+
fixtures,
252+
"./no#fragment/#/",
253+
path.join(fixtures, "no.js") + "#fragment/#/"
254+
);
255+
256+
testResolve(
257+
"handle fragment escaping",
258+
fixtures,
259+
"./no\0#fragment/\0#/\0##fragment",
260+
path.join(fixtures, "no\0#fragment/\0#", "\0#.js") + "#fragment"
261+
);
262+
242263
it("should correctly resolve", function (done) {
243264
const issue238 = path.resolve(fixtures, "issue-238");
244265

0 commit comments

Comments
 (0)