Is There a Built-in Javascript Function Similar to Os. Path. Join?

Is there a built-in javascript (client-side) function that functions similarly to Node's path.join? I know I can join strings in the following manner:

['a', 'b'].join('/')

The problem is that if the strings already contain a leading/trailing "/", then they will not be joined correctly, e.g.:

['a/','b'].join('/')
1

10 Answers

Use the path module. path.join is exactly what you're looking for. From the docs:

path.join([path1][, path2][, ...])# Join all arguments together and normalize the resulting path.

Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.

Example:

const path = require('node:path')
  
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..')
// returns
'/foo/bar/baz/asdf'

path.join('foo', {}, 'bar')
// throws exception
TypeError: Arguments to path.join must be strings

You can also use import path from 'path' instead of const path = require('node:path') if you're loading modules with that style.

Edit:

I assumed here that you're using server-side Javascript like node.js. If you want to use it in the browser, you can use path-browserify.

2

Building on @Berty's reply, this ES6 variant preserves all leading slashes, to work with protocol relative url's (like //stackoverflow.com), and also ignores any empty parts:

build_path = (...args) => {
  return args.map((part, i) => {
    if (i === 0) {
      return part.trim().replace(/[\/]*$/g, '')
    } else {
      return part.trim().replace(/(^[\/]*|[\/]*$)/g, '')
    }
  }).filter(x=>x.length).join('/')
}
  • build_path("", "my", "path") will return ""
  • build_path("//a", "", "/", "/b/") will return "//a/b"
  • build_path() will return ""

Note that this regex strips trailing slashes. Sometimes a trailing slash carries semantic meaning (e.g. denoting a directory rather than a file), and that distinction will be lost here.

2

There isn't currently a built-in that will perform a join while preventing duplicate separators. If you want concise, I'd just write your own:

function pathJoin(parts, sep){
   var separator = sep || '/';
   var replace   = new RegExp(separator+'{1,}', 'g');
   return parts.join(separator).replace(replace, separator);
}

var path = pathJoin(['a/', 'b', 'c//'])
2

The accepted answer doesn't work for URLs, it removes the double slash after the protocol
becomes https:/hostname.

Most other answers do not handle the first and last part differently. A slash at the beginning or end should not be removed, it would change the meaning (relative/absolute) (file/directory) of the joined path.

Below is a modified version of the accepted answer:

function pathJoin(parts, sep){
    const separator = sep || '/';
    parts = parts.map((part, index)=>{
        if (index) {
            part = part.replace(new RegExp('^' + separator), '');
        }
        if (index !== parts.length - 1) {
            part = part.replace(new RegExp(separator + '$'), '');
        }
        return part;
    })
    return parts.join(separator);
 }

usage:

console.log(pathJoin([' 'hostname', 'path/'])); // '
console.log(pathJoin(['relative/', 'path', 'to/dir/']));  // 'relative/path/to/dir/'
console.log(pathJoin(['/absolute/', 'path', 'to/file'])); // '/absolute/path/to/file'
4

My approach to solve this problem:

var path = ['a/','b'].map(function (i) {
    return i.replace(/(^\/|\/$)/, '');
}).join('/');

Second method:

var path = ['a/','b'].join('/').replace(/\/{2,}/, '/')
3

You may find the code on this gist "Simple path join and dirname functions for generic javascript" useful (i.e both in node and browser)

// Joins path segments.  Preserves initial "/" and resolves ".." and "."
// Does not support using ".." to go above/outside the root.
// This means that join("foo", "../../bar") will not resolve to "../bar"
function join(/* path segments */) {
  // Split the inputs into a list of path commands.
  var parts = [];
  for (var i = 0, l = arguments.length; i < l; i++) {
    parts = parts.concat(arguments[i].split("/"));
  }
  // Interpret the path commands to get the new resolved path.
  var newParts = [];
  for (i = 0, l = parts.length; i < l; i++) {
    var part = parts[i];
    // Remove leading and trailing slashes
    // Also remove "." segments
    if (!part || part === ".") continue;
    // Interpret ".." to pop the last segment
    if (part === "..") newParts.pop();
    // Push new path segments.
    else newParts.push(part);
  }
  // Preserve the initial slash if there was one.
  if (parts[0] === "") newParts.unshift("");
  // Turn back into a single string path.
  return newParts.join("/") || (newParts.length ? "/" : ".");
}

// A simple function to get the dirname of a path
// Trailing slashes are ignored. Leading slash is preserved.
function dirname(path) {
  return join(path, "..");
}

Note similar implementations (which may be transformed to js code as well) exist for php here

2

There is not, however it is pretty easy to implement. This could also be solved with a regex but its not too bad without one.

var pathJoin = function(pathArr){
    return pathArr.map(function(path){
        if(path[0] === "/"){
            path = path.slice(1);        
        }
        if(path[path.length - 1] === "/"){
            path = path.slice(0, path.length - 1);   
        }
        return path;     
    }).join("/");
}

This one makes sure it works with http:// links without removing the double slash. It trims the slashes at the beginning and end of each part. Then joins them seperated by '/'

/**
 * Joins 2 paths together and makes sure there aren't any duplicate seperators
 * @param parts the parts of the url to join. eg: [' '/my-custom/path/']
 * @param separator The separator for the path, defaults to '/'
 * @returns {string} The combined path
 */
function joinPaths(parts, separator) {
  return parts.map(function(part) { return part.trim().replace(/(^[\/]*|[\/]*$)/g, ''); }).join(separator || '/');
}

Building on what @leo did:

export function buildPath(...args: string[]): string {
  const [first] = args;
  const firstTrimmed = first.trim();
  const result = args
    .map((part) => part.trim())
    .map((part, i) => {
      if (i === 0) {
        return part.replace(/[/]*$/g, '');
      } else {
        return part.replace(/(^[/]*|[/]*$)/g, '');
      }
    })
    .filter((x) => x.length)
    .join('/');

  return firstTrimmed === '/' ? `/${result}` : result;
}

Should cover following scenarios:

  [
    {
      input: ['/'],
      result: '/',
    },
    {
      input: ['/', 'aaa', ':id'],
      result: '/aaa/:id',
    },
    {
      input: ['/bbb', ':id'],
      result: '/bbb/:id',
    },
    {
      input: ['ccc', ':id'],
      result: 'ccc/:id',
    },
    {
      input: ['/', '/', '/', '/ddd/', '/', ':id'],
      result: '/ddd/:id',
    },
    {
      input: ['', '', '', 'eee', '', ':id'],
      result: 'eee/:id',
    },
  ];

All of the other solutions contain bugs, are difficult to understand, and support features that are redundant in the browser, so I wrote my own (derived from the others). This version adds support for file URLs and removes support for Windows paths.

const join = function(...parts) {

    /* This function takes zero or more strings, which are concatenated
    together to form a path or URL, which is returned as a string. This
    function intelligently adds and removes slashes as required, and is
    aware that `file` URLs will contain three adjacent slashes. */

    const [first, last, slash] = [0, parts.length - 1, "/"];

    const matchLeadingSlash = new RegExp("^" + slash);
    const matchTrailingSlash = new RegExp(slash + "$");

    parts = parts.map(function(part, index) {

        if (index === first && part === "file://") return part;

        if (index > first) part = part.replace(matchLeadingSlash, "");

        if (index < last) part = part.replace(matchTrailingSlash, "");

        return part;
    });

    return parts.join(slash);
};

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Sophia Al-Mansoor

Sophia Al-Mansoor

Global Business & E-Commerce Reporter

Sophia analyzes international trade, startup ecosystems, retail transformation, and supply chain logistics for modern digital publications.

Share this article
Twitter Facebook Pinterest