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('/') 110 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.
2Building 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.
2There 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//']) 2The 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' 4My 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,}/, '/') 3You 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
2There 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); };