0

The standard URL object can be used to calculate an absolute URL from a relative URL and a base URL as follows.

const base = 'http://example.com/'
const relative = '/foo/bar?quux=123'
const absolute = new URL(relative, base).href
console.assert(absolute === 'http://example.com/foo/bar?quux=123')

However, I could not figure out how to use the URL object to do the reverse.

const base = 'http://example.com/'
const absolute = 'http://example.com/foo/bar?quux=123'
const relative = '???'
console.assert(relative === '/foo/bar?quux=123')

Do the browser APIs provide a standardised way for constructing relative URLs or do I need to use a 3rd party solution?

cyberixae
  • 843
  • 5
  • 15
  • 1
    Have a look at the `window.location` object - it's all in there - if you look at this page in the console, the `pathname` key is what you're after – StudioTime Mar 06 '20 at 10:28

3 Answers3

1

Do the browser APIs provide a standardised way for constructing relative URLs?

Yes, they do. You already used it, URL

Alternatively, you can create a temporary <a>-element and get the values from that. A freshly created <a>-element or URL both implement location, so you can extract location-properties:

// use <a href ...>
const absolute = `http://example.com/foo/bar?quux=123`;
const hrefTmp = document.createElement(`a`);
hrefTmp.href = absolute;
console.log(`Absolute from <a>: ${hrefTmp.href}`);
console.log(`Relative from <a>: ${hrefTmp.pathname}${hrefTmp.search}`);

// using URL
const url = new URL(absolute);
console.log(`Absolute from url: ${url.href}`);
console.log(`Relative from url: ${url.pathname}${url.search}`);

// using URL with a different base path
const baseOther = `http://somewhere.eu`;
const urlOther = new URL(`${url.pathname}${url.search}`, baseOther );
console.log(`Absolute from urlOther: ${urlOther.href}`);
console.log(`Relative from urlOther: ${urlOther.pathname}${urlOther.search}`);
.as-console-wrapper { top: 0; max-height: 100% !important; }
KooiInc
  • 119,216
  • 31
  • 141
  • 177
0

I ended up doing the following.

const base = 'http://example.com/'
const absolute = 'http://example.com/foo/bar?quux=123'
const relative = ((temp) => {
  return absolute.startsWith(base) ? temp.pathname.concat(temp.search) : temp.href
})(new URL(absolute, base))
console.assert(relative === '/foo/bar?quux=123')
cyberixae
  • 843
  • 5
  • 15
0

There's an npm module called relateurl that works well but its dependency on url (note lower-case) causes mild trouble in the latest Webpack and React. I published another called relativize-url that uses URL (shouty-caps), which is supported everywhere. It's pretty minimal so you can install it or just steal the code from index.js.

const components = [
  {name: 'protocol', write: u => u.protocol },
  {name: 'hostname', write: u => '//' + u.hostname },
  {name: 'port', write: u => u.port === '' ? '' : (':' + u.port) },
  {name: 'pathname', write: (u, frm, relativize) => {
    if (!relativize) return u.pathname;
    const f = frm.pathname.split('/').slice(1);
    const t = u.pathname.split('/').slice(1);
    const maxDepth = Math.max(f.length, t.length);
    let start = 0;
    while(start < maxDepth && f[start] === t[start]) ++start;
    const rel = f.slice(start+1).map(c => '..')
          .concat(t.slice(start)).join('/');
    return rel.length <= u.pathname.length ? rel : u.pathname
  }},
  {name: 'search', write: u => u.search },
  {name: 'hash', write: u => u.hash},
];

function relativize (rel, base, opts = {}) { // opts not yet used
  const from = new URL(base);
  const to = new URL(rel, from);
  let ret = '';
  for (let component of components) {
    if (ret) { // force abs path if e.g. host was diffferent
      ret += component.write(to, from, false);
    } else if (from[component.name] !== to[component.name]) {
      ret = component.write(to, from, true);
    }
  }
  return ret;
}

The pathname handler has extra code in it to give you nice minimal relative paths. Give it some exercise:

const base = 'http://a.example/b/e/f?g=h#i'
const target = 'http://a.example/b/c/d?j=k#l'
console.log(relativize(target, base))
// got '../c/d'; let's check it:
console.log(new URL('../c/d', base).href === target)
// true
console.log(relativize('http://a.example/b?a=b','http://a.example/b?c=d'))
// ?a=b
console.log(relativize('http://a.example/b#asdf', 'http://a.example/b'))
// #asdf
console.log(relativize('http://a.example/b', 'http://c.example/d'))
// //a.example/b

Please report bugs in https://github.com/ericprud/relativize-url/issues .

ericP
  • 1,675
  • 19
  • 21