These are a few thoughts on how Node.js apps can make data portable between platforms. The focus in this case is on paths. Handling line terminators is covered elsewhere.
Feedback welcome!
Sometimes we’d like to use the same paths on different platforms. Then there are two issues that we are facing:
- The path separator may be different.
- The file structure may be different: home directories and directories for temporary files may be in different locations, etc.
As an example, consider a Node.js app that operates on a directory with data. Let’s assume that the app can be configured with two kinds of paths:
- Fully qualified paths anywhere on the system
- Paths inside the data directory
Due to the aforementioned issues:
-
We can’t reuse fully qualified paths between platforms.
- Sometimes we need absolute paths. These have to be configured per “instance” of the data directory and stored externally (or inside it and ignored by version control). These paths stay put and are not moved with the data directory.
-
We can reuse paths that point into the data directory. Such paths may be stored in configuration files (inside the data directory or not) and in constants in the app code. To do that:
- We have to store them as relative paths.
- We have to ensure that the path separator is correct on each platform.
The next subsection explains how both can be achieved.
Relative platform-independent paths can be stored as Arrays of path segments and turned into fully qualified platform-specific paths as follows:
const universalRelativePath = ['static', 'img', 'logo.jpg'];
const dataDirUnix = '/home/john/data-dir';
assert.equal(
path.posix.resolve(dataDirUnix, ...universalRelativePath),
'/home/john/data-dir/static/img/logo.jpg'
);
const dataDirWindows = 'C:\\Users\\jane\\data-dir';
assert.equal(
path.win32.resolve(dataDirWindows, ...universalRelativePath),
'C:\\Users\\jane\\data-dir\\static\\img\\logo.jpg'
);
To create relative platform-specific paths, we can use:
const dataDir = '/home/john/data-dir';
const pathInDataDir = '/home/john/data-dir/static/img/logo.jpg';
assert.equal(
path.relative(dataDir, pathInDataDir),
'static/img/logo.jpg'
);
The following function converts relative platform-specific paths into platform-independent paths:
import * as path from 'node:path';
function splitRelativePathIntoSegments(relPath) {
if (path.isAbsolute(relPath)) {
throw new Error('Path isn’t relative: ' + relPath);
}
relPath = path.normalize(relPath);
const result = [];
while (true) {
const base = path.basename(relPath);
if (base.length === 0) break;
result.unshift(base);
const dir = path.dirname(relPath);
if (dir === '.') break;
relPath = dir;
}
return result;
}
Using splitRelativePathIntoSegments()
on Unix:
> splitRelativePathIntoSegments('static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')
[ 'file.txt' ]
Using splitRelativePathIntoSegments()
on Windows:
> splitRelativePathIntoSegments('static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('C:static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')
[ 'file.txt' ]
> splitRelativePathIntoSegments('C:file.txt')
[ 'file.txt' ]