2

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?

1
  • 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 Commented Mar 6, 2020 at 10:28

3 Answers 3

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; }

Sign up to request clarification or add additional context in comments.

2 Comments

I was thinking about a case where the base URL is available as a const but is not necessarily the current URL.
Sure, appended some code to the snippet for that scenario. It's all about the location, implemented by URL or a <a>-element
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')

1 Comment

This doesn't take into account the case where the relative url needs to go up in the directory tree. I.e. base = 'http://example.com/foo/' and absolute = 'http://example.com/bar/baz' should result in '../bar/baz'
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 .

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.