Here is @m93a's answer adapted to work without modifying the URL class, and with typescript types...
/**
* Returns relative URL from base to target.
* @param target
* @param base
* @param reload `false` to use "#" whenever the path and query of this url and the base are equal,
* `true` to use filename or query (forces reload in browsers)
* @param root `true` to force root-relative paths (e.g. "/dir/file"),
* `false` to force directory-relative paths (e.g. "../file"),
* `undefined` to always use the shorter one.
* @param dotSlash whether or not to include the "./" in relative paths.
*/
export function uriRelativize(target: string | URL, base: string | URL, reload?: boolean, root?: boolean, dotSlash?: boolean): string {
if (!(target instanceof URL))
target = new URL(target);
if (!(base instanceof URL))
base = new URL(base);
reload = !!reload;
dotSlash = !!dotSlash;
var rel = "";
if (target.protocol !== base.protocol) {
return target.href;
}
if (target.host !== base.host ||
target.username !== base.username ||
target.password !== base.password) {
rel = "//";
if (target.username) {
rel += target.username;
if (target.password)
rel += ":" + target.password;
rel += "@";
}
rel += target.host;
rel += target.pathname;
rel += target.search;
rel += target.hash;
return rel;
}
if (target.pathname !== base.pathname) {
if (root) {
rel = target.pathname;
} else {
var thisPath = target.pathname.split("/");
var basePath = base.pathname.split("/");
var tl = thisPath.length;
var bl = basePath.length;
for (var i = 1, l = Math.min(tl, bl) - 1; i < l; i++) {
if (thisPath[i] !== basePath[i]) {
break;
}
}
for (var cd = bl - 1; cd > i; cd--) {
if (!rel && dotSlash) {
rel += "./";
}
rel += "../";
}
if (dotSlash && !rel)
rel += "./";
for (l = tl; i < l; i++) {
rel += thisPath[i];
if (i !== l - 1) {
rel += "/";
}
}
if (root !== false && rel.length > target.pathname.length) {
rel = target.pathname;
}
if (!rel && basePath[basePath.length - 1]) {
rel = "./";
}
}
}
if (rel || target.search !== base.search) {
rel += target.search;
rel += target.hash;
}
if (!rel) {
if (reload) {
rel = target.search || "?";
rel += target.hash;
} else {
rel = target.hash || "#";
}
}
return rel;
}