02 node:path 核心 API

1. join() 与 resolve()

join() 拼接并规范化片段:

path.join("storage", "jobs", "42", "result.zip");

它不保证得到绝对路径。resolve() 从右向左解析,直到形成绝对路径:

path.resolve("storage", "jobs", "42");

若后面的片段本身是绝对路径,前面的部分会被丢弃:

path.resolve("D:\\safe", "C:\\outside");

因此 resolve(root, userInput) 不是安全校验,只是路径计算。

2. 拆解路径

const parsed = path.parse("docs/guide/start.md");

包含:

dir   docs/guide
base  start.md
name  start
ext   .md

生成对应 HTML:

const htmlPath = path.join(
  parsed.dir,
  `${parsed.name}.html`,
);

这能保留目录层级,不能只调用:

path.basename(inputPath)

否则不同目录下的 index.md 会互相覆盖。

3. relative() 用于比较边界

const relative = path.relative(root, target);

若目标在根目录外,结果通常以 .. 开头,或者在 Windows 上成为绝对路径。

function isInsideRoot(root: string, target: string): boolean {
  const relative = path.relative(root, target);

  return (
    relative !== "" &&
    !relative.startsWith(`..${path.sep}`) &&
    relative !== ".." &&
    !path.isAbsolute(relative)
  );
}

是否允许 target === root 应由调用者明确决定,删除操作通常不应允许。

4. 高频 API

API 作用 常见用途
dirname() 父目录 创建输出父目录
basename() 最后一段 获取展示文件名
extname() 扩展名 初步分类,不做安全证明
normalize() 规范化分隔和点段 观察路径,不等于安全
isAbsolute() 判断绝对路径 拒绝归档绝对路径
parse() 拆解路径 替换扩展名
format() 由组件构造路径 与 parse() 配合

5. 实操:保持相对层级

function createHtmlOutputPath(
  sourceRoot: string,
  outputRoot: string,
  markdownPath: string,
): string {
  const relative = path.relative(sourceRoot, markdownPath);
  const parsed = path.parse(relative);

  return path.join(
    outputRoot,
    parsed.dir,
    `${parsed.name}.html`,
  );
}

输入:

source/docs/start.md

输出:

output/docs/start.html

常见错误

练习题

  1. 分别输出五组参数经过 join() 和 resolve() 的结果。
  2. 实现 .md 到 .html 的路径转换,保留目录结构。
  3. 为 isInsideRoot() 编写 Windows 和 POSIX 测试样例。
  4. 解释为什么 resolve(root, input) 之后仍要做边界检查。