Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active September 2, 2022 21:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rauschma/349140fe6f8508b60588f37f97360dc2 to your computer and use it in GitHub Desktop.
Save rauschma/349140fe6f8508b60588f37f97360dc2 to your computer and use it in GitHub Desktop.

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!

Using the same paths on different platforms

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

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' ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment