How To Join Relative URLs in JavaScript
Asked Answered
C

4

34

I want to join two strings each representing a relative URL in Javascript.

I want to join the base URL http://www.adress.com/more/evenmore with the following examples:

  1. ../../adress (with the expected output: http://www.adress.com/adress)
  2. ../adress (with the expected output http://www.adress.com/more/adress)

What would be the best way? I was thinking of using regexp and checking
how many ../ preceed the relative URL, then subtracting that amount from the baseurl and adding them to what is left.

Chorus answered 20/4, 2010 at 15:1 Comment(1)
Please consider changing the accepted answer on this.Godesberg
I
14

The following function decomposes the URL then resolves it.

function concatAndResolveUrl(url, concat) {
  var url1 = url.split('/');
  var url2 = concat.split('/');
  var url3 = [ ];
  for (var i = 0, l = url1.length; i < l; i ++) {
    if (url1[i] == '..') {
      url3.pop();
    } else if (url1[i] == '.') {
      continue;
    } else {
      url3.push(url1[i]);
    }
  }
  for (var i = 0, l = url2.length; i < l; i ++) {
    if (url2[i] == '..') {
      url3.pop();
    } else if (url2[i] == '.') {
      continue;
    } else {
      url3.push(url2[i]);
    }
  }
  return url3.join('/');
}
Intro answered 20/4, 2010 at 15:9 Comment(3)
I modified this where your testing for '.' to also test for empty string url1[i] == '.' || url1[i] == '' so if the passed in URLs have slashes it still composes a valid URL. +1Colburn
this doesn't seem to work as expected. /a + ./b = /a/b and it should be /bHeady
@GordonTruslove: Maybe this solves your issue, it gets rid of the a. url1[ url1.length-1 ] = ''Insecurity
M
90

8 years later, many browsers (except for Internet Explorer) support the URL constructor (URL(url [, base])).

> new URL('../address', 'http://www.adress.com/more/evenmore/').href
"http://www.adress.com/more/address"
> new URL('../../address', 'http://www.adress.com/more/evenmore/').href
"http://www.adress.com/address"
Machzor answered 4/7, 2018 at 7:46 Comment(2)
Here's a polyfill for IE.Allege
It should be noted that relative does not mean relative to the full path but relative to the last directory path. new URL('bar', 'http://example.com/foo) will result in http://example.com/bar not http://example.com/foo/barCatholicism
I
14

The following function decomposes the URL then resolves it.

function concatAndResolveUrl(url, concat) {
  var url1 = url.split('/');
  var url2 = concat.split('/');
  var url3 = [ ];
  for (var i = 0, l = url1.length; i < l; i ++) {
    if (url1[i] == '..') {
      url3.pop();
    } else if (url1[i] == '.') {
      continue;
    } else {
      url3.push(url1[i]);
    }
  }
  for (var i = 0, l = url2.length; i < l; i ++) {
    if (url2[i] == '..') {
      url3.pop();
    } else if (url2[i] == '.') {
      continue;
    } else {
      url3.push(url2[i]);
    }
  }
  return url3.join('/');
}
Intro answered 20/4, 2010 at 15:9 Comment(3)
I modified this where your testing for '.' to also test for empty string url1[i] == '.' || url1[i] == '' so if the passed in URLs have slashes it still composes a valid URL. +1Colburn
this doesn't seem to work as expected. /a + ./b = /a/b and it should be /bHeady
@GordonTruslove: Maybe this solves your issue, it gets rid of the a. url1[ url1.length-1 ] = ''Insecurity
B
8

Using URI.js (urijs - npm): absoluteTo():

function joinUrl(baseUrl, url) {
    var theUrl = new URI(url);
    if (theUrl.is("relative")) {
        theUrl = theUrl.absoluteTo(baseUrl);
    }
    return theUrl.toString();
}
Bourke answered 15/6, 2012 at 19:41 Comment(3)
These days there's no real need to use a library.Innermost
This library option works if you have to relative URLs to combine. Because ECMAScript's URL throws an error: new URL("../the_relative_url", "rel/path/") TypeError: URL constructor: rel/path/ is not a valid URL.Bourke
I'd almost want to add that to your answer. That's a great justification for avoiding URL, especially on the front end where access to Node.js native modules isn't supported.Innermost
I
7

The ECMAScript URL Web API mentioned by @ning is a good place to start: especially as it is available in vanilla JS implementations (Node, etc.) and doesn't require you to use a library that does something the implementation nowq already accomplishes. Consulting the MDN documentation, more specifically the examples, is a great place to start.

Borrowing (somewhat) directly from their documentation:

let m = 'https://developer.mozilla.org';

// ... omitted 

let d = new URL('/en-US/docs', m);

If you were to do the above, then console.log the .toString of the constructed URL object, your output would be: 'https://developer.mozilla.org/en-US/docs'.

Importantly, if you consult the Syntax section of the documentation, you will note that the second argument is optional and the second argument (as seen in the above example) represents the base URL (though only in the case of two arguments being supplied).

If both argument values are absolute URLs, Web API honors the first and discards the second.

If you are working with Node.js, I would encourage you to look at the native path module for doing work on relative paths, again over using a library. The main motivation here is that spinning up your own algorithm (probably just a call to path here and there) is potentially better than introducing a library that will pull in several other dependencies that may introduce vulnerabilities and unnecessary bloat to your application (or just be too heavy weight for what you need).

However, if you are working on the front end, you won't have path available to you and - as mentioned in @cburgmer's answer comments - Web API's URL doesn't support the relative path case mentioned. In this case, you may need to look for a library to accomplish this for you; however, again, given the other answers, I'd consider trying out a home-spun approach.

To their credit, URI.js currently only integrates one non-dev. dependency and doesn't have a huge footprint.

Innermost answered 27/5, 2020 at 23:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.