Skip to content

Dotpath Template Tag

When to use

Use dotpath when constructing file paths from dynamic segments and you want cleaner syntax than path.join() calls. The tagged template literal lets you write paths naturally with interpolation while handling segment joining correctly.

The pattern

The dotpath tagged template literal lets you write file paths with interpolation while handling segment joining correctly. It delegates to path.join(), which normalizes separators and resolves . and .. segments.

Building paths with dynamic segments

Construct a query file path from a model name. The tagged template reads like a real path instead of a list of string arguments:

javascript
const model = "account";
const queryFile = dotpath`./models/${model}/queries/list.sql`;
// => "models/account/queries/list.sql"

Combine multiple variables into a single output path without manual join() calls:

javascript
const base = "./dist";
const version = "v2";
const file = "bundle.js";
const output = dotpath`${base}/${version}/${file}`;
// => "dist/v2/bundle.js"

Comparing with path.join()

Both dotpath and path.join() produce the same result, but the template tag preserves the visual structure of the path:

javascript
// Without the template tag
const p1 = join("./models", model, "queries", "list.sql");

// With the template tag
const p2 = dotpath`./models/${model}/queries/list.sql`;

Implementing dotpath()

The function collects the static string parts and interpolated values from the tagged template, then joins them as path segments:

javascript
import { join } from "node:path";

export function dotpath(strings, ...values) {
  const maxLength = Math.max(
    strings.length, values.length
  );
  const parts = [];
  let i = 0;
  while (i < maxLength) {
    if (i < strings.length) parts.push(strings[i]);
    if (i < values.length) parts.push(values[i]);
    i++;
  }
  return join(...parts);
}

The name dotpath reflects the ./ prefix common in relative paths.

Trade-offs

  • Readability vs. discoverability. Tagged templates look elegant but can confuse developers unfamiliar with the pattern. A join() call is more explicit.
  • No validation. The function joins whatever you pass — it does not check that the result is a valid or safe path.
  • path.join() normalizes away leading ./. The input ./models/account becomes models/account. This is usually fine but matters if you rely on the leading dot for relative resolution elsewhere.
  • Platform separators. path.join() uses the OS separator (/ on POSIX, \ on Windows). If you need consistent forward slashes (for URLs or cross-platform config), use path.posix.join() instead.