要找出是什么导致 Node 项目在 macOS 上的安装体积变大,可以测量已安装的软件包大小,然后通过 npm 的依赖树反向追踪最大的候选包。在终端中,从项目目录开始:du 显示磁盘使用情况,npm ls 列出已安装的依赖树,而 npm explain 则说明为什么会存在某个软件包。它们解答了不同的问题:大小指出了占用空间的内容;依赖链则指出了是什么引入了它。
在 macOS 上测量已安装的软件包
打开终端,切换到项目文件夹,在测量前先确认该目录包含 node_modules。以下命令适用于 macOS 默认的 zsh,并且避免了对 shell 通配符行为的依赖:
cd "/path/to/your/project"
ls -ld node_modules
find node_modules -mindepth 1 -maxdepth 1 -type d -prune -exec du -sh {} + | sort -h
du -sh 会为每个直接子目录输出易于人类阅读的总计大小,而 sort -h 则按从由小到大的顺序对这些总计进行排序。从底部往上看即可找到体积最大的项。find 命令对每个顶级目录只测量一次:例如,它会将 node_modules/@example 作为一个合并的作用域目录来报告,而不是分别报告每个带作用域的包。这是一次有用的初步筛查,而不是各个依赖项单独所占开销的报告。
如果命令提示 node_modules 不存在,则该项目可能尚未安装依赖树,或者您可能处于错误的目录中。在为了测量而安装任何内容之前,请先检查项目的包管理器和 lockfile。npm 的 lockfile 描述的是解析后的依赖树,而 du 测量的是已安装的文件。npm 的 package-lock 文档
将庞大目录追溯至其依赖链
使用 npm ls 检查 npm 的逻辑依赖树。其默认输出侧重于顶级包;--all 可以将列表展开至传递依赖项。npm ls 文档
npm ls --all
对于大型项目,完整的列表可能会显得非常冗长繁杂。一旦您从依赖树中找到了候选包名,就可以询问 npm 为什么要安装它:
npm explain package-name
将 package-name 替换为实际名称,例如 some-library。输出结果将沿着依赖链指向根项目。如果 npm 报告了多个副本或版本,请在列表中找出相关的安装位置,并将该路径传递给 npm explain,例如:
npm explain node_modules/parent-package/node_modules/package-name
npm 官方文档说明了可以使用 npm explain 来追踪导致某个软件包被安装的调用链,包括依赖项嵌套时的特定文件夹。npm explain 文档
检查带作用域的包和嵌套文件夹
第一次测量将诸如 @example 之类的作用域归入一个目录。要检查其直接包目录,请运行:
find node_modules/@example -mindepth 1 -maxdepth 1 -type d -prune -exec du -sh {} + | sort -h
将 @example 替换为您在第一次测量中看到的作用域。如果您使用的是变量或包含空格的路径,请为作用域路径加上引号。对于不带作用域的包,可以直接测量其文件夹,例如 du -sh "node_modules/some-library"。软件包的文件夹可能包含其自己的嵌套依赖;如果将父目录和嵌套目录的总计相加,会重复计算某些文件。请使用这些数据来定位排查对象,而不是用来计算每个分支的精确占用比例。
接下来,针对大体积的包或测量目录中显示的嵌套包路径运行 npm explain。如果说明信息指向某个直接依赖,那么该顶级包就是需要调查的分支。一个体积庞大的传递依赖包也可能被多个分支共享,因此它出现在某个路径下并不一定意味着只有该直接依赖在使用它。npm 将 npm ls 描述为一个逻辑树,它可能与 node_modules 的物理布局不同;请使用 explain 的输出内容来理解彼此之间的关系,而不是将文件夹嵌套视作完整的关系图。
区分开发依赖与生产依赖
一个庞大的开发工具在生产环境安装中可能是不必要的,而应用本身可能必须依赖一个庞大的运行时依赖项。请在 npm 的输出中检查该调用链,看它是源自开发依赖还是生产依赖。如果您想对比仅针对生产环境的 npm 安装,npm ls --omit=dev 可以在磁盘安装视图中忽略开发依赖;它不会将它们从 package.json 或 lockfile 中移除。在自动化中使用省略选项之前,请先确认 npm 的行为和版本。npm 的 install 文档
应将体积庞大视为一个线索,而不是证明该包是不必要的证据。在移除或替换某个依赖项之前,请核对您的应用程序导入、构建脚本和部署流程具体需要什么。如果存在 lockfile,在比较不同安装时请随时留意它:它记录了解析后的依赖树,有助于解释为什么另一次安装可能与当前文件夹有所不同。
实用的阶段性目标
当您能够指出测得的最大目录、确认其中的一个或多个包,并使用 npm explain 将其追溯到引入它们的根依赖项时,您就已经得到了有价值的答案。这就为您提供了一个可供审查的具体分支。仅凭磁盘占用情况无法断定移除该分支是否安全,而且在共享文件或依赖项时,npm 的依赖命令也不会计算出某个包的独占份额。
参考来源
npm-ls: 列出已安装的包 — npm
npm-explain: 解释已安装的包 — npm
package-lock.json — npm
npm-install: 安装软件包 — npm
Mac 版终端使用手册 — Apple