[d565449] | 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
|
---|