工具模块

文件位置

src/utils/ — 包含 4 个辅助工具模块。

get-package-base.js

// src/utils/get-package-base.js
const pkgNameRegEx = /^(@[^\\\/]+[\\\/])?[^\\\/]+/;
module.exports = function(id) {
  const pkgIndex = id.lastIndexOf('node_modules');
  if (pkgIndex !== -1 && /* 边界检查 */) {
    const pkgNameMatch = id.substr(pkgIndex + 13).match(pkgNameRegEx);
    if (pkgNameMatch)
      return id.substr(0, pkgIndex + 13 + pkgNameMatch[0].length);
  }
};

功能:从文件绝对路径中提取包的根目录。

算法

  1. 找到路径中最后一个 node_modules
  2. 验证其前后都有路径分隔符
  3. 提取包名(支持 @scope/name 格式)
  4. 返回从路径开头到包名结束的子串

示例

  • /project/node_modules/express/lib/router.js/project/node_modules/express
  • /project/node_modules/@babel/core/index.js/project/node_modules/@babel/core

pkgNameRegEx 也作为命名导出暴露,供其他模块使用。

has-type-module.js

// src/utils/has-type-module.js
exports.hasTypeModule = function hasTypeModule(path) {
  while (path !== (path = resolve(path, '..'))) {
    try {
      return JSON.parse(readFileSync(resolve(path, 'package.json'))).type === 'module';
    } catch (e) {
      if (e.code === 'ENOENT') continue;
      throw e;
    }
  }
}

功能:判断给定路径是否位于 "type": "module" 的包边界内。

算法

  1. 从给定路径开始,逐级向上遍历目录
  2. 在每个目录尝试读取 package.json
  3. 如果找到,检查其 type 字段是否为 "module"
  4. 如果 package.json 不存在(ENOENT),继续向上
  5. 到达文件系统根目录时停止

使用场景

  • src/index.js:决定默认的 ESM/CJS 输出模式
  • src/cli.js:计算输出文件扩展名

注意eval('resolve') 的使用是为了防止 ncc 在编译自身时处理这个 require.resolve 路径。

ncc-cache-dir.js

// src/utils/ncc-cache-dir.js
const cacheBase = process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
const projectKey = crypto.hash("sha1", process.cwd());
module.exports = path.join(cacheBase, "ncc", projectKey);

功能:确定 Webpack 文件系统缓存的存储路径。

路径计算

  1. 基础目录:$XDG_CACHE_HOME(如设置)或 ~/.cache
  2. 项目隔离:使用 process.cwd() 的 SHA-1 哈希
  3. 最终路径:<base>/ncc/<sha1>

设计意图:每个项目(通过工作目录区分)有独立的缓存空间。不同项目的缓存不会互相干扰。

使用场景

  • src/index.js:作为 Webpack cache.cacheDirectory 的默认值
  • src/cli.jscache dir/cache clean/cache size 命令

shebang.js

// src/utils/shebang.js
module.exports = /^#![^\n\r]*[\r\n]/;

功能:匹配文件开头的 shebang 行。

正则说明

  • ^#! — 必须在文件开头
  • [^\n\r]* — shebang 内容(不含换行)
  • [\r\n] — 以换行符结尾

使用场景

  • src/index.js:99:读取入口文件时检测 shebang
  • src/cli.js:296:写入输出文件时设置可执行权限
  • src/cli.js:6:被 require 并使用

如果输入有 shebang,输出文件会设置 0o777 权限(可执行),否则为 0o666