1 | // Matches the scheme of a URL, eg "http://"
|
---|
2 | const schemeRegex = /^[\w+.-]+:\/\//;
|
---|
3 | /**
|
---|
4 | * Matches the parts of a URL:
|
---|
5 | * 1. Scheme, including ":", guaranteed.
|
---|
6 | * 2. User/password, including "@", optional.
|
---|
7 | * 3. Host, guaranteed.
|
---|
8 | * 4. Port, including ":", optional.
|
---|
9 | * 5. Path, including "/", optional.
|
---|
10 | * 6. Query, including "?", optional.
|
---|
11 | * 7. Hash, including "#", optional.
|
---|
12 | */
|
---|
13 | const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/;
|
---|
14 | /**
|
---|
15 | * File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start
|
---|
16 | * with a leading `/`, they can have a domain (but only if they don't start with a Windows drive).
|
---|
17 | *
|
---|
18 | * 1. Host, optional.
|
---|
19 | * 2. Path, which may include "/", guaranteed.
|
---|
20 | * 3. Query, including "?", optional.
|
---|
21 | * 4. Hash, including "#", optional.
|
---|
22 | */
|
---|
23 | const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;
|
---|
24 | function isAbsoluteUrl(input) {
|
---|
25 | return schemeRegex.test(input);
|
---|
26 | }
|
---|
27 | function isSchemeRelativeUrl(input) {
|
---|
28 | return input.startsWith('//');
|
---|
29 | }
|
---|
30 | function isAbsolutePath(input) {
|
---|
31 | return input.startsWith('/');
|
---|
32 | }
|
---|
33 | function isFileUrl(input) {
|
---|
34 | return input.startsWith('file:');
|
---|
35 | }
|
---|
36 | function isRelative(input) {
|
---|
37 | return /^[.?#]/.test(input);
|
---|
38 | }
|
---|
39 | function parseAbsoluteUrl(input) {
|
---|
40 | const match = urlRegex.exec(input);
|
---|
41 | return makeUrl(match[1], match[2] || '', match[3], match[4] || '', match[5] || '/', match[6] || '', match[7] || '');
|
---|
42 | }
|
---|
43 | function parseFileUrl(input) {
|
---|
44 | const match = fileRegex.exec(input);
|
---|
45 | const path = match[2];
|
---|
46 | return makeUrl('file:', '', match[1] || '', '', isAbsolutePath(path) ? path : '/' + path, match[3] || '', match[4] || '');
|
---|
47 | }
|
---|
48 | function makeUrl(scheme, user, host, port, path, query, hash) {
|
---|
49 | return {
|
---|
50 | scheme,
|
---|
51 | user,
|
---|
52 | host,
|
---|
53 | port,
|
---|
54 | path,
|
---|
55 | query,
|
---|
56 | hash,
|
---|
57 | type: 7 /* Absolute */,
|
---|
58 | };
|
---|
59 | }
|
---|
60 | function parseUrl(input) {
|
---|
61 | if (isSchemeRelativeUrl(input)) {
|
---|
62 | const url = parseAbsoluteUrl('http:' + input);
|
---|
63 | url.scheme = '';
|
---|
64 | url.type = 6 /* SchemeRelative */;
|
---|
65 | return url;
|
---|
66 | }
|
---|
67 | if (isAbsolutePath(input)) {
|
---|
68 | const url = parseAbsoluteUrl('http://foo.com' + input);
|
---|
69 | url.scheme = '';
|
---|
70 | url.host = '';
|
---|
71 | url.type = 5 /* AbsolutePath */;
|
---|
72 | return url;
|
---|
73 | }
|
---|
74 | if (isFileUrl(input))
|
---|
75 | return parseFileUrl(input);
|
---|
76 | if (isAbsoluteUrl(input))
|
---|
77 | return parseAbsoluteUrl(input);
|
---|
78 | const url = parseAbsoluteUrl('http://foo.com/' + input);
|
---|
79 | url.scheme = '';
|
---|
80 | url.host = '';
|
---|
81 | url.type = input
|
---|
82 | ? input.startsWith('?')
|
---|
83 | ? 3 /* Query */
|
---|
84 | : input.startsWith('#')
|
---|
85 | ? 2 /* Hash */
|
---|
86 | : 4 /* RelativePath */
|
---|
87 | : 1 /* Empty */;
|
---|
88 | return url;
|
---|
89 | }
|
---|
90 | function stripPathFilename(path) {
|
---|
91 | // If a path ends with a parent directory "..", then it's a relative path with excess parent
|
---|
92 | // paths. It's not a file, so we can't strip it.
|
---|
93 | if (path.endsWith('/..'))
|
---|
94 | return path;
|
---|
95 | const index = path.lastIndexOf('/');
|
---|
96 | return path.slice(0, index + 1);
|
---|
97 | }
|
---|
98 | function mergePaths(url, base) {
|
---|
99 | normalizePath(base, base.type);
|
---|
100 | // If the path is just a "/", then it was an empty path to begin with (remember, we're a relative
|
---|
101 | // path).
|
---|
102 | if (url.path === '/') {
|
---|
103 | url.path = base.path;
|
---|
104 | }
|
---|
105 | else {
|
---|
106 | // Resolution happens relative to the base path's directory, not the file.
|
---|
107 | url.path = stripPathFilename(base.path) + url.path;
|
---|
108 | }
|
---|
109 | }
|
---|
110 | /**
|
---|
111 | * The path can have empty directories "//", unneeded parents "foo/..", or current directory
|
---|
112 | * "foo/.". We need to normalize to a standard representation.
|
---|
113 | */
|
---|
114 | function normalizePath(url, type) {
|
---|
115 | const rel = type <= 4 /* RelativePath */;
|
---|
116 | const pieces = url.path.split('/');
|
---|
117 | // We need to preserve the first piece always, so that we output a leading slash. The item at
|
---|
118 | // pieces[0] is an empty string.
|
---|
119 | let pointer = 1;
|
---|
120 | // Positive is the number of real directories we've output, used for popping a parent directory.
|
---|
121 | // Eg, "foo/bar/.." will have a positive 2, and we can decrement to be left with just "foo".
|
---|
122 | let positive = 0;
|
---|
123 | // We need to keep a trailing slash if we encounter an empty directory (eg, splitting "foo/" will
|
---|
124 | // generate `["foo", ""]` pieces). And, if we pop a parent directory. But once we encounter a
|
---|
125 | // real directory, we won't need to append, unless the other conditions happen again.
|
---|
126 | let addTrailingSlash = false;
|
---|
127 | for (let i = 1; i < pieces.length; i++) {
|
---|
128 | const piece = pieces[i];
|
---|
129 | // An empty directory, could be a trailing slash, or just a double "//" in the path.
|
---|
130 | if (!piece) {
|
---|
131 | addTrailingSlash = true;
|
---|
132 | continue;
|
---|
133 | }
|
---|
134 | // If we encounter a real directory, then we don't need to append anymore.
|
---|
135 | addTrailingSlash = false;
|
---|
136 | // A current directory, which we can always drop.
|
---|
137 | if (piece === '.')
|
---|
138 | continue;
|
---|
139 | // A parent directory, we need to see if there are any real directories we can pop. Else, we
|
---|
140 | // have an excess of parents, and we'll need to keep the "..".
|
---|
141 | if (piece === '..') {
|
---|
142 | if (positive) {
|
---|
143 | addTrailingSlash = true;
|
---|
144 | positive--;
|
---|
145 | pointer--;
|
---|
146 | }
|
---|
147 | else if (rel) {
|
---|
148 | // If we're in a relativePath, then we need to keep the excess parents. Else, in an absolute
|
---|
149 | // URL, protocol relative URL, or an absolute path, we don't need to keep excess.
|
---|
150 | pieces[pointer++] = piece;
|
---|
151 | }
|
---|
152 | continue;
|
---|
153 | }
|
---|
154 | // We've encountered a real directory. Move it to the next insertion pointer, which accounts for
|
---|
155 | // any popped or dropped directories.
|
---|
156 | pieces[pointer++] = piece;
|
---|
157 | positive++;
|
---|
158 | }
|
---|
159 | let path = '';
|
---|
160 | for (let i = 1; i < pointer; i++) {
|
---|
161 | path += '/' + pieces[i];
|
---|
162 | }
|
---|
163 | if (!path || (addTrailingSlash && !path.endsWith('/..'))) {
|
---|
164 | path += '/';
|
---|
165 | }
|
---|
166 | url.path = path;
|
---|
167 | }
|
---|
168 | /**
|
---|
169 | * Attempts to resolve `input` URL/path relative to `base`.
|
---|
170 | */
|
---|
171 | function resolve(input, base) {
|
---|
172 | if (!input && !base)
|
---|
173 | return '';
|
---|
174 | const url = parseUrl(input);
|
---|
175 | let inputType = url.type;
|
---|
176 | if (base && inputType !== 7 /* Absolute */) {
|
---|
177 | const baseUrl = parseUrl(base);
|
---|
178 | const baseType = baseUrl.type;
|
---|
179 | switch (inputType) {
|
---|
180 | case 1 /* Empty */:
|
---|
181 | url.hash = baseUrl.hash;
|
---|
182 | // fall through
|
---|
183 | case 2 /* Hash */:
|
---|
184 | url.query = baseUrl.query;
|
---|
185 | // fall through
|
---|
186 | case 3 /* Query */:
|
---|
187 | case 4 /* RelativePath */:
|
---|
188 | mergePaths(url, baseUrl);
|
---|
189 | // fall through
|
---|
190 | case 5 /* AbsolutePath */:
|
---|
191 | // The host, user, and port are joined, you can't copy one without the others.
|
---|
192 | url.user = baseUrl.user;
|
---|
193 | url.host = baseUrl.host;
|
---|
194 | url.port = baseUrl.port;
|
---|
195 | // fall through
|
---|
196 | case 6 /* SchemeRelative */:
|
---|
197 | // The input doesn't have a schema at least, so we need to copy at least that over.
|
---|
198 | url.scheme = baseUrl.scheme;
|
---|
199 | }
|
---|
200 | if (baseType > inputType)
|
---|
201 | inputType = baseType;
|
---|
202 | }
|
---|
203 | normalizePath(url, inputType);
|
---|
204 | const queryHash = url.query + url.hash;
|
---|
205 | switch (inputType) {
|
---|
206 | // This is impossible, because of the empty checks at the start of the function.
|
---|
207 | // case UrlType.Empty:
|
---|
208 | case 2 /* Hash */:
|
---|
209 | case 3 /* Query */:
|
---|
210 | return queryHash;
|
---|
211 | case 4 /* RelativePath */: {
|
---|
212 | // The first char is always a "/", and we need it to be relative.
|
---|
213 | const path = url.path.slice(1);
|
---|
214 | if (!path)
|
---|
215 | return queryHash || '.';
|
---|
216 | if (isRelative(base || input) && !isRelative(path)) {
|
---|
217 | // If base started with a leading ".", or there is no base and input started with a ".",
|
---|
218 | // then we need to ensure that the relative path starts with a ".". We don't know if
|
---|
219 | // relative starts with a "..", though, so check before prepending.
|
---|
220 | return './' + path + queryHash;
|
---|
221 | }
|
---|
222 | return path + queryHash;
|
---|
223 | }
|
---|
224 | case 5 /* AbsolutePath */:
|
---|
225 | return url.path + queryHash;
|
---|
226 | default:
|
---|
227 | return url.scheme + '//' + url.user + url.host + url.port + url.path + queryHash;
|
---|
228 | }
|
---|
229 | }
|
---|
230 |
|
---|
231 | export { resolve as default };
|
---|
232 | //# sourceMappingURL=resolve-uri.mjs.map
|
---|