本文主要介绍基于npm包管理器的组件成分解析原理。
npm(全称Node Package Manager)是Node.js标准的软件包管理器。
npm的依赖管理文件是package.json,开发者可以在package.json中指定每个依赖项的版本范围。
如果一个项目中存在package.json文件,便可以执行npm install命令自动安装和维护当前项目所需的所有模块并生成package-lock.json文件。
package.json完整文件结构如下:
{ "name": "screeps", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "push": "rollup -cw --environment DEST:main", "build": "rollup -cw --environment DEST:local", }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-node-resolve": "^13.1.1", "@types/lodash": "^3.10.1", "@types/screeps": "^3.2.4", "rollup": "^2.61.1", "rollup-plugin-clear": "^2.0.7", "rollup-plugin-copy": "^3.4.0", "rollup-plugin-screeps": "^1.0.1", "rollup-plugin-typescript2": "^0.31.1", "typescript": "^4.5.4" }, "dependencies": { "source-map": "^0.6.1" } }
其中name为项目名,version为项目版本,license为项目声明的许可证,devDependencies为开发环境使用的依赖,dependencies为生产环境使用的依赖。
依赖写法为"name":"version",版本可以指定准确版本或一个范围,范围需遵循semver语义化版本规范(详见:https://semver.org/)。
package-lock.json是在npm install时自动生成的文件,用以记录当前状态下实际安装的各个npm package的具体来源和版本号,通过该文件可以准确定位到npm项目的依赖及版本。所以优先解析package-lock.json文件。
package-lock.json文件结构如下:
{ "name": "foo", "version": "1.0.0", "dependencies": { "b": { "version": "1.2.1" }, "a": { "version": "2.1.5", "requires": { "b": "^1.1.9" } }, "c": { "version": "1.3.1", "requires": { "b": "^1.2.0" } } } }
其中name字段为项目名称,version字段为项目版本。dependencies字段中包含项目使用的所有直接和间接依赖,而且记录了组件间的依赖关系。
例如:
"b": { "version": "1.2.1" },
代表组件b的版本号为1.2.1。
"a": { "version": "2.1.5", "requires": { "b": "^1.1.9" } },
代表项目依赖2.1.5版本的组件a,该组件依赖版本约束为^1.1.9的组件b。
同理可知项目依赖1.3.1版本的组件c,该组件依赖版本约束为^1.2.0的组件b。
从<package-lock.json文件结构>可看出组件a和组件c都没有被其他组件所依赖,所以可知这两个组件是项目的直接依赖。
仅通过package-lock.json无法确定组件b是否是直接依赖,可以结合package.json文件进一步确定,没有package.json时,将b当作间接依赖处理。若一个组件同时为直接和间接依赖,按直接依赖处理。
注:
^1.1.9代表版本号需要>=1.1.9且<2.0.0;
^1.2.0代表版本号需要>=1.2.0且<2.0.0;
更多约束格式请参阅semver官网
由此可以构建出当前项目的依赖结构:
实线代表直接依赖,虚线代表间接依赖。
package.json为开发者编写管理的依赖管理文件,在未找到package-lock.json文件时将解析该文件。
package.json仅包含直接依赖,在项目构建时会从npm仓库下载需要的间接依赖并构建为package-lock.json文件,因此可以模拟npm构建流程来获取项目引用的组件依赖。
package.json文件结构如下:
{ "name": "foo", "version": "1.0.0", "devDependencies": { "a": "^2.0.0" }, "dependencies": { "c": "^1.1.0" } }
dependencies为项目实际使用的直接依赖,devDependencies为项目开发时使用的直接依赖。
例如:
"devDependencies": { "a": "^2.0.0" }
代表项目开发过程中依赖版本约束为^2.0.0的组件a。
"dependencies": { "c": "^1.1.0" }
代表项目直接依赖版本约束为^1.1.0组件c。
分析到这里我们可以总结出如下图依赖关系:
通过该依赖关系可以看出项目组件的直接依赖及组件的版本范围,但无法得知组件依赖的具体版本。
在没有package-lock.json文件的情况下,为了进一步获取依赖的准确版本及间接依赖,需要从npm仓库下载对应组件的详细信息。
例如组件a的详细信息结构为:
{ "time": { "2.1.5": "2011-02-16T22:31:02.088Z", "3.1.1": "2011-04-10T12:23:22.088Z" }, "versions": { "2.1.5": { "dependencies": { "b": "^1.1.9" } }, "3.1.1": { "dependencies": { "b": "^2.2.0" } } } }
其中time字段为组件所有版本及发布日期,根据约束从这里获取约束范围内的最大版本。
versions字段为组件各个版本对应的详细信息,其中dependencies字段为组件的依赖信息。
对于本例来说,组件a的约束为^2.0.0,要求版本号>=2.0.0且<3.0.0,所以选择2.1.5版本。因此组件依赖结构就变成了:
按照这种方式层级解析便可获取整个项目的依赖信息。
感谢每一位开源社区成员对OpenSCA的支持和贡献。
OpenSCA的代码会在GitHub和Gitee持续迭代,欢迎Star和PR,成为我们的开源贡献者,也可提交问题或建议至Issues。我们会参考大家的建议不断完善OpenSCA开源项目,敬请期待更多功能的支持。
GitHub:
https://github.com/XmirrorSecurity/OpenSCA-cli/releases
Gitee:
https://gitee.com/XmirrorSecurity/OpenSCA-cli/releases
OpenSCA官网:
https://opensca.xmirror.cn/