vercel/nft

GitHub: vercel/nft

一个通过静态分析精确追踪 Node.js 应用运行时所需全部文件及依赖的命令行工具与库。

Stars: 1658 | Forks: 179

# Node File Trace 用于精确确定应用程序 runtime 所需的所有文件(包括 `node_modules`)。 这类似于 [@vercel/ncc](https://npmjs.com/package/@vercel/ncc),不同之处在于它不执行打包,因此不依赖 webpack。这在不移动任何静态资源或二进制文件的情况下,实现了相同的 tree-shaking 优势。 ## 用法 ### 安装 ``` npm i @vercel/nft ``` ### 用法 提供源文件列表作为输入: ``` const { nodeFileTrace } = require('@vercel/nft'); const files = ['./src/main.js', './src/second.js']; const { fileList } = await nodeFileTrace(files); ``` 文件列表将包含应用程序代码可能需要的所有 `node_modules` 模块和静态资源。 ### 选项 #### 基础 文件列表的 base 路径 - 所有文件都将作为相对于此 base 的路径提供。 默认使用 `process.cwd()`: ``` const { fileList } = await nodeFileTrace(files, { base: process.cwd(), }); ``` 在列表和分析中,位于 `base` 之上的任何文件/文件夹都会被忽略。 #### Process Cwd 在应用分析时,某些函数依赖于 `process.cwd()` 的值,例如 `path.resolve('./relative')`,甚至直接调用 `process.cwd()`。 设置 `processCwd` 选项可以引导此分析使用正确的路径,以确保正确检测到静态资源。 ``` const { fileList } = await nodeFileTrace(files, { processCwd: path.resolve(__dirname), }); ``` 默认情况下,`processCwd` 与 `base` 相同。 #### Exports & Imports 默认情况下,支持对 [Node.js "exports" 和 "imports" 字段](https://nodejs.org/dist/latest-v14.x/docs/api/esm.html#esm_package_entry_points)的追踪,并按照定义追踪 `"node"`、`"require"`、`"import"` 和 `"default"` 条件。 或者,也可以提供明确的条件列表: ``` const { fileList } = await nodeFileTrace(files, { conditions: ['node', 'production'], }); ``` 在指定确切的导出条件列表时,只有 `"node"` 导出应被显式包含(如果需要)。无论设置了什么自定义条件,`"require"`、`"import"` 和 `"default"` 条件都将始终按定义被追踪。 在 Node.js 22 或更高版本中进行追踪时,`"module-sync"` 条件默认是启用的。设置 `moduleSyncCatchall` 可以同时追踪 `module-sync` 分支和正常的 runtime/fallback 分支: ``` const { fileList } = await nodeFileTrace(files, { moduleSyncCatchall: true, }); ``` #### Exports Only 在追踪导出时,对于不支持 `"exports"` 的 Node.js 版本,仍会追踪 `"main"` / index 字段。 这可以通过 `exportsOnly` 选项来禁用: ``` const { fileList } = await nodeFileTrace(files, { exportsOnly: true, }); ``` 这样,任何带有 `"exports"` 的 package 都将只追踪其导出内容,而完全不会包含 main。当目标环境为 [Node.js 12.17.0](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V12.md#12.17.0) 或更高版本时,这可以减小输出体积。 #### Paths 要使用的自定义解析路径定义。 ``` const { fileList } = await nodeFileTrace(files, { paths: { 'utils/': '/path/to/utils/', }, }); ``` 尾部斜杠映射目录,确切路径仅映射确切路径。 #### Hooks 可以通过将以下 FS 函数作为选项传入来进行挂钩(hook): - `readFile(path): Promise` - `stat(path): Promise` - `readlink(path): Promise` - `resolve(id: string, parent: string): Promise` ##### 高级解析 当提供自定义 resolve hook 时,您有责任根据 `id` 输入返回一个或多个已解析文件的绝对路径。然而,在某些情况下,您可能只想增强或覆盖特定情况下的解析行为。您可以通过导入 `nft` 底层的 resolver 来使用它。内置的 `resolve` 函数需要从 hook 中转发一些额外的参数: - `resolve(id: string, parent: string, job: Job, isCjs: boolean): Promise` 以下示例展示了一个 id 被解析为自定义路径,而所有其他路径都由内置 resolver 进行解析: ``` const { nodeFileTrace, resolve } = require('@vercel/nft'); const files = ['./src/main.js', './src/second.js']; const { fileList } = await nodeFileTrace(files, { resolve: async (id, parent, job, isCjs) => { if (id === './src/main.js') { return '/path/to/some/resolved/main/file.js'; } else { return resolve(id, parent, job, isCjs); } }, }); ``` #### TypeScript 内部解析默认支持在追踪中解析 `.ts` 文件。 由于其集成了现有构建系统的特性,TypeScript 编译器并未包含在本项目中 - 而是 TypeScript 转换层需要单独集成到 `readFile` hook 中。 #### File IO Concurrency 在一些大型项目中,文件追踪逻辑可能会同时处理许多文件。在这种情况下,如果您不限制并发文件 IO 的数量,很可能会发生 OOM(内存不足)问题。 我们对 fs 操作默认使用 1024 的并发数,以在性能和内存使用之间取得平衡。您可以将此值增加到更大的数字以获得更快的速度,但要注意如果并发量过高会引发内存问题。 ``` const { fileList } = await nodeFileTrace(files, { fileIOConcurrency: 2048, }); ``` #### 分析 Analysis 选项允许自定义应执行多少分析,以精确计算出依赖列表。 默认情况下,会尽可能多地进行详细分析,以确保追踪结果中不会遗漏任何可能需要的文件。 要禁用所有分析,请设置 `analysis: false`。或者,可以通过以下方式自定义各个分析选项: ``` const { fileList } = await nodeFileTrace(files, { // default analysis: { // whether to glob any analysis like __dirname + '/dir/' or require('x/' + y) // that might output any file in a directory emitGlobs: true, // whether __filename and __dirname style // expressions should be analyzed as file references computeFileReferences: true, // evaluate known bindings to assist with glob and file reference analysis evaluatePureExpressions: true, }, }); ``` #### Ignore 可以提供自定义忽略规则来跳过文件包含(因此也会跳过对该文件中引用的分析)。 ``` const { fileList } = await nodeFileTrace(files, { ignore: ['./node_modules/pkg/file.js'], }); ``` Ignore 也接受函数或 glob。 请注意,提供给 ignore 的路径是相对于 `base` 的。 #### Cache 要在构建之间持久化文件缓存,请传入一个空的 `cache` 对象: ``` const cache = Object.create(null); const { fileList } = await nodeFileTrace(['index.ts'], { cache }); // later: { const { fileList } = await nodeFileTrace(['index.ts'], { cache }); } ``` 请注意,不支持缓存失效,因此前提是文件系统在多次运行之间不会发生改变。 #### Depth `depth` 选项控制从入口点文件开始追踪 module graph 的层级数。这对于限制追踪范围或进行调试很有用: ``` const { fileList } = await nodeFileTrace(files, { depth: 2, }); ``` 如果 `depth: 0`,则追踪结果仅包含入口点文件本身,不包含任何额外的模块。如果 `depth: 1`,则会追踪入口点及其直接 import 的模块,依此类推。 请注意,`depth` 选项不能小于 0。设置为负值会导致错误。 #### Reasons 为了获取单个文件被包含的底层原因,输出结果中还提供了一个 `reasons` 对象: ``` const { fileList, reasons } = await nodeFileTrace(files); ``` `reasons` 输出将是一个如下形式的对象: ``` { [file: string]: { type: 'dependency' | 'asset' | 'sharedlib', ignored: true | false, parents: string[] } } ``` `reasons` 还包括被忽略的文件(标记为 `ignored: true`),以及它们的 `ignoreReason`。 每个文件都是因为被另一个文件引用而被包含的。`parents` 列表将包含导致该文件被包含的所有文件的列表。
标签:GNU通用公共许可证, MITM代理, Node.js, SOC Prime, 代码分析, 依赖追踪, 凭证管理, 开发工具, 数据可视化, 自定义脚本