evanw/esbuild · error

Rel: can't make {targpath} relative to {basepath}

Error message

Rel: can't make {targpath} relative to {basepath}

What it means

esbuild vendors filepath.Rel in internal/fs/filepath.go. This branch returns the error when Rel cannot relate targpath to basepath because the two paths are on different volumes (e.g. different Windows drive letters C: vs D:) or one is volume-rooted/absolute-slashed and the other is not. Rel fundamentally requires both paths to share a volume and rootedness.

Source

Thrown at internal/fs/filepath.go:593

// Rel calls Clean on the result.
func (fp goFilepath) rel(basepath, targpath string) (string, error) {
	baseVol := fp.volumeName(basepath)
	targVol := fp.volumeName(targpath)
	base := fp.clean(basepath)
	targ := fp.clean(targpath)
	if fp.sameWord(targ, base) {
		return ".", nil
	}
	base = base[len(baseVol):]
	targ = targ[len(targVol):]
	if base == "." {
		base = ""
	}
	// Can't use IsAbs - `\a` and `a` are both relative in Windows.
	baseSlashed := len(base) > 0 && base[0] == fp.pathSeparator
	targSlashed := len(targ) > 0 && targ[0] == fp.pathSeparator
	if baseSlashed != targSlashed || !fp.sameWord(baseVol, targVol) {
		return "", errors.New("Rel: can't make " + targpath + " relative to " + basepath)
	}
	// Position base[b0:bi] and targ[t0:ti] at the first differing elements.
	bl := len(base)
	tl := len(targ)
	var b0, bi, t0, ti int
	for {
		for bi < bl && base[bi] != fp.pathSeparator {
			bi++
		}
		for ti < tl && targ[ti] != fp.pathSeparator {
			ti++
		}
		if !fp.sameWord(targ[t0:ti], base[b0:bi]) {
			break
		}
		if bi < bl {
			bi++
		}

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Ensure both paths are on the same volume/drive (e.g. move outdir under the same drive as servedir).
  2. Pass absolute paths consistently to both arguments so rootedness matches.
  3. On Windows, normalize drive letters / UNC prefixes so they match between the two paths.
  4. If using esbuild serve, keep AbsOutputDir and Servedir on the same filesystem root.

Example fix

// before (Windows, cross-volume)
ctx.Serve({ Servedir: "C:\\www", /* outdir = D:\\build" })

// after (same volume)
ctx.Serve({ Servedir: "C:\\www", /* outdir = C:\\www\\build" })
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function sameVolume(a, b) {
  const ra = path.resolve(a), rb = path.resolve(b);
  // Windows: compare drive letter / UNC root
  return ra.split(path.sep)[0].toLowerCase() === rb.split(path.sep)[0].toLowerCase();
}
if (!sameVolume(servedir, outdir)) throw new Error('cross-volume paths');

Try / catch

try { await ctx.Serve({ Servedir, /* outdir set */ }); } catch (e) { if (/can't make .* relative to/.test(e.message)) { /* move outdir onto same volume as servedir */ } throw e; }

Prevention

When it happens

Trigger: rel(basepath, targpath) is called where baseVol != targVol (different drive letters / UNC hosts) or where one path begins with a path separator and the other does not (baseSlashed != targSlashed). In esbuild this surfaces from computeRelativePath when the output directory and serve directory live on different volumes, or from watch-file relativization across drives.

Common situations: On Windows, outdir on D:\ while servedir is on C:\ (or vice versa); mixing a UNC path (\\server\share) with a drive path; one path passed as relative (no leading slash) while the other is absolute; cross-volume builds where the project and build output are on different mounts.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/4d665f392fe1ee76.json. Report an issue: GitHub.