Denormalizing path separator in Node.js under Windows - node.js

In Windows, path module of Node.js produces all paths with backslash. It seems like it calls its own normalize() function when other functions (like resolve()) are called.
Is there a way to de-normalize the paths (with forward slash), without explicit replacement all over the place?
I tried setting path.sep = '/', but resolve() still produces paths with \\.

You can path.sep or path.parse to disassemble path and then join it as you like.

I had that problem with Windows 10. I resolved it with this script:
var path = require('path');
path.join2 = path.join;
path.sep = '/';
path.join = function(){
var res = path.join2.apply({}, arguments);
res = res.replace(/\\/g, path.sep);
return res;
}
//rest of your code

Related

Alternative for __dirname in Node.js when using ES6 modules

I use the flag --experimental-modules when running my Node application in order to use ES6 modules.
However when I use this flag the metavariable __dirname is not available. Is there an alternative way to get the same string that is stored in __dirname that is compatible with this mode?
As of Node.js 10.12 there's an alternative that doesn't require creating multiple files and handles special characters in filenames across platforms:
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
The most standardized way in 2021
import { URL } from 'url'; // in Browser, the URL in native accessible on window
const __filename = new URL('', import.meta.url).pathname;
// Will contain trailing slash
const __dirname = new URL('.', import.meta.url).pathname;
And forget about join to create paths from the current file, just use the URL
const pathToAdjacentFooFile = new URL('./foo.txt', import.meta.url).pathname;
const pathToUpperBarFile = new URL('../bar.json', import.meta.url).pathname;
For Node 10.12 +...
Assuming you are working from a module, this solution should work, and also gives you __filename support as well
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
The nice thing is that you are also only two lines of code away from supporting require() for CommonJS modules. For that you would add:
import { createRequireFromPath } from 'module';
const require = createRequireFromPath(__filename);
In most cases, using what is native to Node.js (with ES Modules), not external resources, the use of __filename and __dirname for most cases can be totally unnecessary. Most (if not all) of the native methods for reading (streaming) supports the new URL + import.meta.url, exactly as the official documentation itself suggests:
No __filename or __dirname
No JSON Module Loading
No require.resolve
As you can see in the description of the methods, the path parameter shows the supported formats, and in them include the <URL>, examples:
Method
path param supports
fs.readFile(path[, options], callback)
<string>, <Buffer>, <URL>, <integer>
fs.readFileSync(path[, options])
<string>, <Buffer>, <URL>, <integer>
fs.readdir(path[, options], callback)
<string>, <Buffer>, <URL>
fs.readdirSync(path[, options])
<string>, <Buffer>, <URL>, <integer>
fsPromises.readdir(path[, options])
<string>, <Buffer>, <URL>
fsPromises.readFile(path[, options])
<string>, <Buffer>, <URL>, <FileHandle>
So with new URL('<path or file>', import.meta.url) it solves and you don't need to be treating strings and creating variables to be concatenated later.
Examples:
See how it is possible to read a file at the same level as the script without needing __filename or any workaround:
import { readFileSync } from 'fs';
const output = readFileSync(new URL('./foo.txt', import.meta.url));
console.log(output.toString());
List all files in the script directory:
import { readdirSync } from 'fs';
readdirSync(new URL('./', import.meta.url)).forEach((dirContent) => {
console.log(dirContent);
});
Note: In the examples I used the synchronous functions just to make it easier to copy and execute.
If the intention is to make a "own log" (or something similar) that will depend on third parties, it is worth some things done manually, but within the language and Node.js this is not necessary, with ESMODULES it is totally possible not to depend on either __filename and neither __dirname, since native resources with new URL with already solve it.
Note that if you are interested in using something like require at strategic times and need the absolute path from the main script, you can use module.createRequire(filename) (Node.js v12.2.0 + only) combined with import.meta.url to load scripts at levels other than the current script level, as this already helps to avoid the need for __dirname, an example using import.meta.url with module.createRequire:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// foo-bar.js is a CommonJS module.
const fooBar = require('./foo-bar');
fooBar();
Source from foo-bar.js:
module.exports = () => {
console.log('hello world!');
};
Which is similar to using without "ECMAScript modules":
const fooBar = require('./foo-bar');
There have been proposals about exposing these variables through import.meta, but for now, you need a hacky workaround that I found here:
// expose.js
module.exports = {__dirname};
// use.mjs
import expose from './expose.js';
const {__dirname} = expose;
I used:
import path from 'path';
const __dirname = path.resolve(path.dirname(decodeURI(new URL(import.meta.url).pathname)));
decodeURI was important: used spaces and other stuff within the path on my test system.
path.resolve() handles relative urls.
edit:
fix to support windows (/C:/... => C:/...):
import path from 'path';
const __dirname = (() => {let x = path.dirname(decodeURI(new URL(import.meta.url).pathname)); return path.resolve( (process.platform == "win32") ? x.substr(1) : x ); })();
I made this module es-dirname that will return the current script dirname.
import dirname from 'es-dirname'
console.log(dirname())
It works both in CommonJs scripts and in ES Modules both on Windows and Linux.
Open an issue there if have an error as the script has been working so far in my projects but it might fail in some other cases. For this reason do not use it in a production environment. And this is a temporary solution as I am sure the Node.js team will release a robust way to do it in a near future.
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// do not use the following code which is bad for CJK characters
const __filename = new URL('', import.meta.url).pathname;
import path from 'path';
const __dirname = path.join(path.dirname(decodeURI(new URL(import.meta.url).pathname))).replace(/^\\([A-Z]:\\)/, "$1");
This code also works on Windows. (the replacement is safe on other platforms, since path.join returns back-slash separators only on Windows)
Since other answers, while useful, don't cover both cross-platform cases (Windows POSIX) and/or path resolution other than the __dirname or __filename and it's kind of verbose to repeat this kind of code everywhere:
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const somePath = join(__dirname, '../some-dir-or-some-file')
I just published a NPM package called esm-path to help with this kind of recurring task, hoping it can also be useful to others.
It's documented but here how to use it:
import { getAbsolutePath } from 'esm-path'
const currentDirectoryPath = getAbsolutePath(import.meta.url)
console.log(currentDirectoryPath)
const parentDirectoryPath = getAbsolutePath(import.meta.url, '..')
console.log(parentDirectoryPath)
// Adapt the relative path to your case
const packageJsonFilePath = getAbsolutePath(import.meta.url, '../package.json')
console.log(packageJsonFilePath)
// Adapt the relative path to your case
const packageJsonFilePath = getAbsolutePath(import.meta.url, '..' , 'package.json')
console.log(packageJsonFilePath)
Just use path.resolve() method.
import { resolve } from 'path';
app.use('/public/uploads', express.static(resolve('public', 'uploads')))
I use this option, since the path starts with file:// just remove that part.
const __filename = import.meta.url.slice(7);
const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/"));
As Geoff pointed out the following code returns not the module's path but working directory.
import path from 'path';
const __dirname = path.resolve();
works with --experimental-modules
create a file called root-dirname.js in your project root with this in it:
import { dirname } from 'path'
const dn = dirname(new URL(import.meta.url).hostname)
const __dirname = process.platform === 'win32' ? dn.substr(1) : dn // remove the leading slash on Windows
export const rootDirname = __dirname
Then just import rootDirname when you want the path to the project root folder.
Other than that, Rudolf Gröhling's answer is also correct.
You can use the stack from a new Error(). The error doesn't need to be thrown, and won't stop program execution either. The first line of the stack will always be the error and its message, with the second line being the file the which the error was invoked from.
Since this is a method (which is probably in a util.js file), the real location of the getDirname() call is actually the third line of the error stack.
export const getDirname = () => {
// get the stack
const { stack } = new Error();
// get the third line (the original invoker)
const invokeFileLine = stack.split(`\n`)[2];
// match the file URL from file://(.+)/ and get the first capturing group
// the (.+) is a greedy quantifier and will make the RegExp expand to the largest match
const __dirname = invokeFileLine.match(/file:\/\/(.+)\//)[1];
return __dirname;
};
another option
import {createRequire} from 'module'; // need node v12.2.0
const require = createRequire(import.meta.url);
const __dirname = require.resolve.paths('.')[0];
I have also published a package on NPM called cross-dirname (forked from es-dirname). The package is tested with Node.js (ESM and CJS), Deno and GJS.
Example:
import dirname from 'cross-dirname'
console.log(dirname())
Agree or disagree with the use of global, I found this to be the easiest way to remember and refactor existing code.
Put somewhere early in your code execution:
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
global.___filename = (path) => {
return fileURLToPath(path);
};
global.___dirname = (path) => {
return dirname(global.___filename(path));
};
And then in whichever file you need dirname or filename:
___filename(import.meta.url)
___dirname(import.meta.url)
Of course if we had macros, I wouldn't need to pass import.meta.url, perhaps there's an improvement.
process.cwd()
From documentation:
The process.cwd() method returns the current working directory of the
Node.js process.

How to read from a specified file which is in a folder below without knowing the absolute path

So I have my project directory:
Here I read ArticleFile:
function _getDataFromFile() {
var jsonArray = csvjson.toObject(fs.readFileSync('ArticleFile.csv', { encoding: 'utf8' }));
var result = [];
for (var idx = 0; idx < jsonArray.length; idx++) {
var currArt = jsonArray[idx];
// if (!checkIfElementIsArticle(currArt)) throw "loaded object IS NOT an article!";
result.push(new Article(currArt.imageLocation, currArt.title, currArt.description, parseInt(currArt.quantity),parseInt(currArt.price)));
}
return result;
}
The problem is if "ArticleFile.csv" is in lets say contentsKopie I have to know the absolute path such as : C:\Users\noone_000\Desktop\BSD\Hausübungen\WebServer\WebServer\contentsKopie\ArticleFile.csv . How can I set the path like: fs.readFileSync('/ContentsCopie/ArticleFile.csv', { encoding: 'utf8' })
PS: csvjson is a module (require("csvjson"))
When you start the path with / it is interpreted as an absolute path. If you want to open a file in sub directory, you can use ./ContentsCopie/ArticleFile.csv
The . in front of the slash means that the path is relative to the current directory.
Conversely, if you needed to go up a level you can prefix your path with ..
Isn't ./contentsKopie/ArticleFile.csv enough?
When using /contentsKopie/ArticleFile.csv it's actually looking in C:/contentsKopie/ArticleFile.csv. So you have to prepend your path with a . to tell it to start at the working directory (WebServer here).
Otherwise, you can have the absolute path of the current file with __dirname then you could compose your path using the path module from node.
var path = require('path');
var article = path.join(__dirname, './contentsKopie/ArticleFile.csv');
This way, you'll get the absolute path of your file.

Nodejs absolute paths in windows with forward slash

Can I have absolute paths with forward slashes in windows in nodejs? I am using something like this :
global.__base = __dirname + '/';
var Article = require(__base + 'app/models/article');
But on windows the build is failing as it is requiring something like C:\Something\Something/apps/models/article. I aam using webpack. So how to circumvent this issue so that the requiring remains the same i.e. __base + 'app/models/src'?
I know it is a bit late to answer but I think my answer will help some visitors.
In Node.js you can easily get your current running file name and its directory by just using __filename and __dirname variables respectively.
In order to correct the forward and back slash accordingly to your system you can use path module of Node.js
var path = require('path');
Like here is a messed path and I want it to be correct if I want to use it on my server. Here the path module do everything for you
var randomPath = "desktop//my folder/\myfile.txt";
var correctedPath = path.normalize(randomPath); //that's that
console.log(correctedPath);
desktop/my folder/myfile.txt
If you want the absolute path of a file then you can also use resolve function of path module
var somePath = "./img.jpg";
var resolvedPath = path.resolve(somePath);
console.log(resolvedPath);
/Users/vikasbansal/Desktop/temp/img.jpg
it's 2020, 5 years from the question was published, but I hope that for somebody my answer will be useful. I've used the replace method, here is my code(express js project):
const viewPath = (path.join(__dirname, '../views/')).replace(/\\/g, '/')
exports.articlesList = function(req, res) {
res.sendFile(viewPath + 'articlesList.html');
}
I finally did it like this:
var slash = require('slash');
var dirname = __dirname;
if (process.platform === 'win32') dirname = slash(dirname);
global.__base = dirname + '/';
And then to require var Article = require(__base + 'app/models/article');. This uses the npm package slash (which replaces backslashes by slashes in paths and handles some more cases)
The accepted answer doesn't actually answer the question most people come here for.
If you're looking to normalize all path separators (possibly for string work), here's what you need.
All the code segments have the node.js built-in module path imported to the path variable.
They also have the variable they work from stored in the immutable variable str, unless otherwise specified.
If you have a string, here's a quick one-liner normalize the string to a forward slash (/):
const answer = path.resolve(str).split(path.sep).join("/");
You can normalize to any other separator by replacing the forward slash (/).
If you want just an array of the parts of the path, use this:
const answer = path.resolve(str).split(path.sep);
Once you're done with your string work, use this to create a path able to be used:
const answer = path.resolve(str);
From an array, use this:
// assume the array is stored in constant variable arr
const answer = path.join(...arr);
I recommend against this, as it is patching node itself, but... well, no changes in how you require things.
(function() {
"use strict";
var path = require('path');
var oldRequire = require;
require = function(module) {
var fixedModule = path.join.apply(path, module.split(/\/|\\/));
oldRequire(fixedModule);
}
})();
This is the approach I use, to save some processing:
const path = require('path');
// normalize based on the OS
const normalizePath = (value: string): string {
return path.sep === '\'
? value.replace(/\\/g, '/')
: value;
}
console.log('abc/def'); // leaves as is
console.log('abc\def'); // on windows converts to `abc/def`, otherwise leave as is
Windows uses \, Linux and mac use / for path prefixes
For Windows : 'C:\\Results\\user1\\file_23_15_30.xlsx'
For Mac/Linux: /Users/user1/file_23_15_30.xlsx
If the file has \ - it is windows, use fileSeparator as \, else use /
let path=__dirname; // or filePath
fileSeparator=path.includes('\')?"\":"/"
newFilePath = __dirname + fileSeparator + "fileName.csv";
Use path module
const path = require("path");
var str = "test\test1 (1).txt";
console.log(str.split(path.sep)) // This is only on Windows

Get file name from absolute path in Nodejs?

How can I get the file name from an absolute path in Nodejs?
e.g. "foo.txt" from "/var/www/foo.txt"
I know it works with a string operation, like fullpath.replace(/.+\//, ''),
but I want to know is there an explicit way, like file.getName() in Java?
Use the basename method of the path module:
path.basename('/foo/bar/baz/asdf/quux.html')
// returns
'quux.html'
Here is the documentation the above example is taken from.
To get the file name portion of the file name, the basename method is used:
var path = require("path");
var fileName = "C:\\Python27\\ArcGIS10.2\\python.exe";
var file = path.basename(fileName);
console.log(file); // 'python.exe'
If you want the file name without the extension, you can pass the extension variable (containing the extension name) to the basename method telling Node to return only the name without the extension:
var path = require("path");
var fileName = "C:\\Python27\\ArcGIS10.2\\python.exe";
var extension = path.extname(fileName);
var file = path.basename(fileName,extension);
console.log(file); // 'python'
var path = require("path");
var filepath = "C:\\Python27\\ArcGIS10.2\\python.exe";
var name = path.parse(filepath).name;
console.log(name); //python
var base = path.parse(filepath).base;
console.log(base); //python.exe
var ext = path.parse(filepath).ext;
console.log(ext); //.exe
For those interested in removing extension from filename, you can use
https://nodejs.org/api/path.html#path_path_basename_path_ext
path.basename('/foo/bar/baz/asdf/quux.html', '.html');
If you already know that the path separator is / (i.e. you are writing for a specific platform/environment), as implied by the example in your question, you could keep it simple and split the string by separator:
'/foo/bar/baz/asdf/quux.html'.split('/').pop()
That would be faster (and cleaner imo) than replacing by regular expression.
Again: Only do this if you're writing for a specific environment, otherwise use the path module, as paths are surprisingly complex. Windows, for instance, supports / in many cases but not for e.g. the \\?\? style prefixes used for shared network folders and the like. On Windows the above method is doomed to fail, sooner or later.
path is a nodeJS module meaning you don't have to install any package for using its properties.
import path from 'path'
const dir_name = path.basename('/Users/Project_naptha/demo_path.js')
console.log(dir_name)
// returns
demo_path.js
In NodeJS, __filename.split(/\|//).pop() returns just the file name from the absolute file path on any OS platform.
Why need to care about remembering/importing an API while this regex approach also letting us recollect our regex skills.
So Nodejs comes with the default global variable called '__fileName' that holds the current file being executed
My advice is to pass the __fileName to a service from any file , so that the retrieval of the fileName is made dynamic
Below, I make use of the fileName string and then split it based on the path.sep. Note path.sep avoids issues with posix file seperators and windows file seperators (issues with '/' and '\'). It is much cleaner. Getting the substring and getting only the last seperated name and subtracting it with the actulal length by 3 speaks for itself.
You can write a service like this (Note this is in typescript , but you can very well write it in js )
export class AppLoggingConstants {
constructor(){
}
// Here make sure the fileName param is actually '__fileName'
getDefaultMedata(fileName: string, methodName: string) {
const appName = APP_NAME;
const actualFileName = fileName.substring(fileName.lastIndexOf(path.sep)+1, fileName.length - 3);
//const actualFileName = fileName;
return appName+ ' -- '+actualFileName;
}
}
export const AppLoggingConstantsInstance = new AppLoggingConstants();

Can I use require("path").join to safely concatenate urls?

Is this safe to use require("path").join to concatenate URLs, for example:
require("path").join("http://example.com", "ok");
//returns 'http://example.com/ok'
require("path").join("http://example.com/", "ok");
//returns 'http://example.com/ok'
If not, what way would you suggest for doing this without writing code full of ifs?
No. path.join() will return incorrect values when used with URLs.
It sounds like you want new URL(). From the WHATWG URL Standard:
new URL('/one', 'http://example.com/').href // 'http://example.com/one'
new URL('/two', 'http://example.com/one').href // 'http://example.com/two'
Note that url.resolve is now marked as deprecated in the Node docs.
As Andreas correctly points out in a comment, url.resolve (also deprecated) would only help if the problem is as simple as the example. url.parse also applies to this question because it returns consistently and predictably formatted fields via the URL object that reduces the need for "code full of ifs". However, new URL() is also the replacement for url.parse.
No, you should not use path.join() to join URL elements.
There's a package for doing that now. So rather than reinvent the wheel, write all your own tests, find bugs, fix them, write more tests, find an edge case where it doesn't work, etc., you could use this package.
url-join
https://github.com/jfromaniello/url-join
Install
npm install url-join
Usage
var urljoin = require('url-join');
var fullUrl = urljoin('http://www.google.com', 'a', '/b/cd', '?foo=123');
console.log(fullUrl);
Prints:
'http://www.google.com/a/b/cd?foo=123'
This can be accomplished by a combination of Node's path and URL:
Require the packages:
const nodeUrl = require('url')
const nodePath = require('path')
Start by making a URL object to work with:
> const myUrl = new nodeUrl.URL('https://example.com')
Use pathname= and path.join to construct any possible combination:
> myUrl.pathname = nodePath.join('/search', 'for', '/something/')
'/search/for/something/'
(you can see how liberal path.join is with arguments)
At this point your URL reflects the ultimate desired result:
> myUrl.toString()
'https://example.com/search/for/something/'
Why this approach?
This technique uses built-in libraries. The less third-party dependencies the better, when it comes to CVEs, maintenance, etc.
Nothing will be more proven or better tested than standard libs.
PS: Never manipulate URLs as strings!
When I review code I'm adamant about never manipulating URLs as strings manually. For one, look how complicated the spec is.
Secondly, the absence/presence of a trailing/prefixed slash (/) should not cause everything to break! You should never do:
const url = `${baseUrl}/${somePath}`
and especially not:
uri: host + '/' + SAT_SERVICE + '/' + CONSTELLATION + '/',
Of which I have seen.
Axios has a helper function that can combine URLs.
function combineURLs(baseURL, relativeURL) {
return relativeURL
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
: baseURL;
}
Source:
https://github.com/axios/axios/blob/fe7d09bb08fa1c0e414956b7fc760c80459b0a43/lib/helpers/combineURLs.js
The WHATWG URL object constructor has a (input, base) version, and the input can be relative using /, ./, ../. Combine this with path.posix.join and you can do anything:
const {posix} = require ("path");
const withSlash = new URL("https://example.com:8443/something/");
new URL(posix.join("a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/something/a/b/c'
new URL(posix.join("./a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/something/a/b/c'
new URL(posix.join("/a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/a/b/c'
new URL(posix.join("../a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/a/b/c'
const noSlash = new URL("https://example.com:8443/something");
new URL(posix.join("./a", "b", "c"), noSlash).toString(); // 'https://example.com:8443/a/b/c'
No! On Windows path.join will join with backslashes. HTTP urls are always forward slashes.
How about
> ["posts", "2013"].join("/")
'posts/2013'
When I tried PATH for concatenating url parts I run into problems.
PATH.join stripes '//' down to '/' and this way invalidates an absolute url (eg. http://... -> http:/...).
For me a quick fix was:
baseurl.replace(/\/$/,"") + '/' + path.replace(/^\//,"") )
or with the solution posted by Colonel Panic:
[pathA.replace(/^\/|\/$/g,""),pathB.replace(/^\/|\/$/g,"")].join("/")
We do it like this:
var _ = require('lodash');
function urlJoin(a, b) {
return _.trimEnd(a, '/') + '/' + _.trimStart(b, '/');
}
If you're using lodash, you can use this simple oneliner:
// returns part1/part2/part3
['part1/', '/part2', '/part3/'].map((s) => _.trim(s, '/')).join('/')
inspired by #Peter Dotchev's answer
If you use Angular, you can use Location:
import { Location } from '#angular/common';
// ...
Location.joinWithSlash('beginning', 'end');
Works only on 2 arguments though, so you have to chain calls or write a helper function to do that if needed.
This is what I use:
function joinUrlElements() {
var re1 = new RegExp('^\\/|\\/$','g'),
elts = Array.prototype.slice.call(arguments);
return elts.map(function(element){return element.replace(re1,""); }).join('/');
}
example:
url = joinUrlElements(config.mgmtServer, '/v1/o/', config.org, '/apps');
There are other working answers, but I went with the following. A little path.join/URL combo.
const path = require('path');
//
const baseUrl = 'http://ejemplo.mx';
// making odd shaped path pieces to see how they're handled.
const pieces = ['way//', '//over/', 'there/'];
//
console.log(new URL(path.join(...pieces), baseUrl).href);
// http://ejemplo.mx/way/over/there/
// path.join expects strings. Just an example how to ensure your pieces are Strings.
const allString = ['down', 'yonder', 20000].map(String);
console.log(new URL(path.join(...allString), baseUrl).href);
// http://ejemplo.mx/down/yonder/20000
By the time posting this answer url.resolve() is deprecated;
I did following to join to path in Nodejs:
const path = require('path');
const url = require('url');
let myUrl = new URL('http://ignore.com');
myUrl.pathname=path.join(firstpath, secondpath);
console.log(myUrl.pathname)
This approach logs correct url path and it works for my case.
What is your opinion about this approach?
Thanks
Typescript custom solution:
export function pathJoin(parts: string[], sep: string) {
return parts
.map(part => {
const part2 = part.endsWith(sep) ? part.substring(0, part.length - 1) : part;
return part2.startsWith(sep) ? part2.substr(1) : part2;
})
.join(sep);
}
expect(pathJoin(['a', 'b', 'c', 'd'], '/')).toEqual('a/b/c/d');
expect(pathJoin(['a/', '/b/', 'c/', 'd'], '/')).toEqual('a/b/c/d');
expect(pathJoin(['http://abc.de', 'users/login'], '/')).toEqual('http://abc.de/users/login');
My solution
path.join(SERVER_URL, imageAbsolutePath).replace(':/','://');
Edit: if you want to support windows enviroments
path.join(SERVER_URL, imageAbsolutePath).replace(/\\/g,'/').replace(':/','://');
The second solution will replace all the backslashes, so url parts like querystring and hash may be altered too, but the topic is joining just the url path, so I don't consider it an issue.
The combination of the built-in path and URL libraries provides the best solution.
The answers above, however, do not handle the case where you have a relative url (ie: "../foo") that you want to add to an existing URL (ie: "http://example.com/test/bar"). Simply doing new URL("../foo","http://example.com/test/bar").href would yield "http://example.com/foo", erroneously (for me) discarding the remainder of the original path.
A simple solution is:
var base = "http://example.com:8080/foo/bar";
var rel = "../test";
var resolved = new URL( path.resolve(new URL(base).pathname, rel ), base ).href;
// Result: http://example.com:8080/foo/test
Note: If you care about URL Search Params (which seems unlikely in this scenario), you would save each of the pieces above to discrete variables and set the new url.searchParams = oldUrl.searchParams before getting the href output.

Resources