ByteNoteByteNote
打完包再拿开依赖目录
字

字节笔记本

2026年10月7日 · 约 7 分钟读完

打完包再拿开依赖目录

API中转
¥120

ncc 把一个 Node 入口和它能静态分析到的依赖打成一份脚本。命令是 ncc build index.js -o dist。跑的是 node dist/index.js。它不代替应用去复制那些运行时才去读的文件。

入口文件名和输出目录是两件事

安装和一条构建命令

可以全局安装 @vercel/ncc,也可以装成开发依赖。全局安装之后,命令在任意目录都能敲。开发依赖则跟项目走,用 npx 或 npm 脚本调用,换机器时不用再装一遍全局包。两种不要各写一套版本。

在项目根目录执行 ncc build index.js -o dist。index.js 是入口,不是输出文件的任意名字。-o dist 是目录。打完之后,依赖被收进 dist/index.js。旁边可能有一份 LICENSE,来自依赖的许可证,不是你的业务代码。没有这份文件,脚本仍可执行。执行命令是 node dist/index.js。不要去跑源码目录里的入口,除非你是在打之前做检查。打完之后的运行对象是输出目录里的那一份。

把命令放进 package.json 的 scripts.build,值就是同一条 ncc build index.js -o dist。之后用 npm run build。脚本名是 build 只是样本的选择。入口若不是 index.js,构建脚本和运行路径要一起改。只改 -o 的目录、不改 node 后面的路径,会去跑一个还不存在的文件。

源码映射和静态文件

调试时在命令后加 --source-map,完整写法是 ncc build index.js -o dist --source-map。映射方便对照压缩后的行,不是运行所必需。生产若不想把映射交出去,构建脚本里就不要加这一项。

对话点明,像 .mdx 这种在执行时才去读的文件,不会因为被打进依赖图就自动出现在 dist。需要的话,在构建之后把它们拷到输出目录,或让运行时的读取路径指向拷贝之后的位置。只打包、不拷贝,上线后读文件会找不到路径。动态 require 一段运行时才知道的模块名,也不在“自动收齐依赖”的范围内。样本适用的是依赖在源码里就能看清的小项目或中等项目。

静态文件要在构建后再拷贝

分发的是 dist,不是 node_modules

单文件的意义是目标机器上不必再安装依赖。这要求那些依赖已经被打进去。原生扩展、运行时下载的二进制,不在这条命令的承诺里。对话没有给出这类文件的处理。遇到它们,不要假设 dist/index.js 已经够用。先在一台没有 node_modules 的目录里执行 node dist/index.js。能跑,再谈分发。不能跑,先看缺的是静态文件还是动态加载。

全局安装和开发依赖不要混用两套命令。开发依赖时,构建脚本里写本地的调用方式,提交锁文件。全局安装只适合自己的机器试一次。换一台没有全局包的机器,脚本会找不到命令。入口文件名出现在构建参数里,也出现在运行路径里。输出目录改成别的名字时,运行命令里的目录要跟着改。许可证文件可以不部署,业务脚本必须部署。

源码映射是给排错看的。映射文件会暴露源码结构。不需要在目标机器上调试时,构建命令里不要带那一项。带上之后,输出目录里不只有一份脚本。部署脚本若只拷贝一份脚本文档,映射会留下。要么一起拷,要么构建时就不生成。

静态资源的例子是文档类文件。运行时按路径去读,打包器看不到这次读取,文件就不会出现在输出目录。构建成功、启动后读文件失败,先看是不是这类路径。动态决定的模块名也是同一类问题。先在没有依赖目录的空目录里运行输出脚本。失败信息会指出缺文件还是缺模块。对话没有承诺原生插件也能打进这一份脚本。

构建从入口脚本出发,把源码里能看清的依赖收进输出目录,并留下同名的主文件。运行时执行的是这个主文件。 不要在打包完成之后还去跑仓库里的原始入口。那一份仍然依赖旁边的模块目录,并不是已经打好的单文件。 安装可以放在全局,也可以放在开发依赖里。交给别人构建时用开发依赖,避免对方机器上没有全局命令。 把同一串命令写进脚本字段。手敲和自动构建若各写一套入口,就会产出两个不同的目录。 输出目录改名时,运行命令里的路径一起改。只改其中一边,会去执行一个还不存在的文件。 许可证文件来自依赖的声明,不是业务代码。缺少它通常不影响启动。发布时按需要决定带不带。 映射文件用来对照压缩之后的行号。不在目标机器上排错,就不要生成。生成了就要决定它进不进发布目录。 映射会暴露源码结构。漏看清单时,它会跟着主文件一起被拷走。清单里写清楚要还是不要。 运行时才读取的文档不会因为被读到,就自动出现在主文件里。构建成功后再按读取路径拷贝。 静态文件拷到错误的层级,进程仍然能启动,第一次读取才失败。所以验收要包含一次真实的读取。 验收时把依赖目录拿开,只留主文件和清单里的静态文件。能跑,才说明单文件已经自洽。 不能跑时,先看缺的是静态文件,还是运行时才拼出来的模块名。后者不会被这条命令自动发现。 原生扩展也不在单文件的承诺里。遇到它们要单独说明,不要写成一切都已打进主文件。 输出变大只说明依赖多,不是构建失败。失败是运行时还去找没有打进去的东西。 目标机器留下运行环境和主文件,以及另拷的静态文件。源码目录和依赖目录留在构建机上。 动态模块若存在,在说明里写出名字。构建日志没有报错,不能当成这些模块也已包含。 入口文件名同时出现在构建参数和运行路径里。改了一处却忘了另一处,输出里不会有你要跑的文件。 开发依赖要写进锁文件。换一台机器先安装,再执行脚本字段里的构建,不要假设全局命令存在。 静态文件的拷贝放在构建成功之后。构建失败就不要拷,避免旧文档配上半成品脚本。 在空目录里只放主文件和静态文件再运行。成功才可以分发。失败信息里的模块名就是没打进去的依赖。 许可证缺失不影响启动。缺的是主文件或静态资源时,才会在启动或第一次读取时暴露。 调试时才加源码映射。加了之后发布清单要单列这一份,否则它会跟着主文件进入目标机器。 小项目适合这条命令,是因为依赖在源码里就能看全。依赖树变深只会让单文件变大,命令本身不用改。 运行前删掉依赖目录。若仍然能跑,说明脚本和静态分析到的依赖已经在主文件里。 拷贝静态文件时保持运行时使用的相对路径。多一层或少一层目录,都会在读取时才报错。 分发物以输出目录为准。源码仓库留在构建的那一台机器上,不要和主文件混在同一个发布包里。

相关文章

分享: