本文是《Nodejs开发进阶G-部署和管理》的一个组成部分,由于篇幅和内容的限制,分离出来独立成文。
概述
一个包含js文件和node_modules文件夹的普通的操作系统文件夹,之所以成其为一个nodejs项目,是因为有了一个package.json文件,它就是nodejs项目的控制和配置文件。它的角色和功能是如此重要,每个nodejs开发者都应该熟悉和掌握。
这个文件的扩展名清晰的表明,这是一个json格式文本文件,这意味着它可以承载层次化和结构化的信息和数据;另外,json简单的编码和组织形式,让程序和真人,都可以方便的阅读和管理(想象一下XML和Protobuf?);这些特性为其扩展和应用提供了很大的灵活性。package的文件名,表明它的主要功用就是描述和控制当前这个软件包。
这里吐槽一下,当初设计JSON的时候,好像只考虑了它用来数据和信息结构,并没有考虑到它被应用的如此广泛,甚至用在程序当中,就缺少了一个非常重要的特性:注释信息。当然这个可以通过在其中随便增加一个无效的key来解决,当显然在加载和处理时,会增加一些额外的工作和资源。
结构和内容
package.json文件,通常是在创建nodejs项目时,使用npm init命令创建的。实际上,开发者完全可以使用标准编辑程序,安装格式规范从空白开始编写一个package.json文件,都是没有问题的,它本质上就是一个JSON格式的纯文本文件。
我们可以使用任意一个文本编辑器,甚至linux的nano编辑命令,打开任意一个nodejs项目的package.json。都可以看到类似下面的内容:
shell
$ nano package.json:
{
"name": "ntest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
这个文件的项目有一定的规范,一般的项目名称都是很好理解的,常见的可能需要管理和编辑的项目包括:
- 名称: 当前nodejs项目名称,这在需要发布为npm时非常重要
- 版本: 项目版本,也是npm版本
- 描述信息: 当前这个项目的描述和注释信息
- main: 主程序入口,这就是可以使用node . 执行程序的原因
- scripts: 通过这个配置信息,可以使用npm来执行,如这里就是npm test .
- keywords: npm发布和搜索时有用
- author: 作者
- license: 许可证类型,常用的开源协议有ISC、MIT等等,只有法律意义
上面这些内容,是项目初始化时,使用npm init命令生成时,默认的内容。后续有一些相关的操作,可能会创建一些新的项目,甚至开发者可以按照规范,自己编辑一些项目,来达到项目控制的目的。
package.json规范完整的内容,和很多相关技术细节,可以参考npm的技术文档如下。
下面我们例举几个常用的项目。
dependencies 依赖
在一个一般或者稍微有点规模的nodejs项目中,package.json中,最重要的一个项目,就是项目依赖软件包集合(dependencies)了,下面是笔者的一个nodejs项目package.json中的依赖项目:
package.json
...
"dependencies": {
"@fastify/cors": "^8.1.0",
"@fastify/formbody": "^7.0.1",
"@fastify/multipart": "^7.1.0",
"@fidm/x509": "^1.2.1",
"async": "^3.2.2",
"autocannon": "^7.5.1",
"cbor": "^8.1.0",
"crc": "^3.8.0",
"eta": "^1.12.3",
"fastify": "^4.5.2",
"fastify-socket.io": "^4.0.0",
"got": "^12.5.3",
"iso-3166-1": "^2.1.1",
"minio": "^7.1.3",
"pg": "^8.7.1",
"pg-cursor": "^2.7.1",
"pg-notify": "^0.0.19",
"pump": "^3.0.0",
"qrcode-svg": "^1.1.0",
"redis": "^4.0.1",
"socket.io": "^4.4.0",
"socket.io-client": "^4.4.0",
"svg-captcha": "^1.4.0",
"unzipper": "^0.10.11"
}
我们看到dependencies在package.json中,是一个对象。对象的key就是软件包的名称,而对象的值,是这个项目中当前使用的这个软件包的版本。一般情况下,npm会试图搜索当前可用的最新版本来安装。很多大型的软件包,还会进行模块化,分为很多小的软件包,减少无效的依赖和磁盘空间,使安装和部署更加精简灵活。
这里面我们还看到很多应用比较广泛的项目,包括pg用于数据库、fastify用于Web框架、socket.io是WebSocket模块等等。
开发模式
一般的npm软件包,在nodejs项目中,使用--save标签,成功安装完成后,可以在dependencies中,建立对应的依赖项目。但我们知道,在开发和部署阶段,我们使用的工具和软件包可能不完全相同。这时npm提供了--save-dev标签,它可以指定这个软件包是使用开发或者测试模式安装的。
在实际操作中,它也会按照正常方式来安装这些npm包。但它会在package.json中,建立另外一个devDependencies的项目,来容纳这些内容。在实际的生产部署环境中,需要指定作为生产环境,则这些项目,将不会被npm install命令自动安装,如下:
NODE_ENV=production npm install
开发模式使用的软件包,通常包括这么几类:
- 测试工具或者框架
- 构建工具
- 代码检查工具
- 开发支持工具
关于软件包依赖,package.json还有peerDependencies,bundleDependencies,optionalDependencies等等,这些笔者尚未有机会接触或者使用,如果哪位读者有相关经验和想法,也可以反馈给笔者或者写个技术文章分享一下。
npm脚本
npm不仅仅是可以用于管理软件包和依赖关系,还可以提高一些开发工程方面的特性。例如,在package.json中,可以定义一个scripts对象,然后结合一致的npm run 命令,来执行一些开发过程中的任务和操作。这些脚本通常被称为npm script。 我们来看一些示例:
js
// package.json内容
{
"scripts": {
"start": "node server.js",
"test": "jest",
"build": "webpack"
}
}
// 对应npm操作
npm run start // 执行node程序
npm run test // 执行测试程序
npm run build // 执行建构和编译
这些脚本是完全可自定义的,因为它们就是可以在shell环境中执行的命令,但npm脚本将它们的使用方式简化并且一致了。笔者认为,npm脚本本质上是软件开发工程的一个最佳实践,可以使软件开发的过程和管理更加规范和一致,有利于对软件开发过程的效率和质量进行有效的保证。
pakage-lock.json
在npm5及更新的版本之后,它为nodejs项目,引入了一个新的特性,用来解决一些版本管理不稳定造成的问题。这个特性的表现,就是使用了一个新的项目相关的配置文件: package-lock.json。从这个名字,我们大致可以理解,它是通过版本锁定的方式,来实现相对固定的版本依赖和稳定关系的。
这个文件配合npm的功能特性,可以实现以下一些作用:
- 版本锁定
这个机制可以确保在不同的开发环境中,使用相同的软件包版本。它会精确记录每个安装的软件包及其依赖项的确切版本号。这样一来当项目进行移植或者重置时,可以确保使用相同的软件包版本,避免重新安装时软件包版本和配置不一致的问题。
- 确保可再现性
在协作开发和持续集成环境中,确保每个团队成员和构建服务器都使用相同的软件包版本非常重要。package-lock.json 的存在可以确保项目的可再现性,即使在不同的开发环境或时间点。
- 加速安装:
package-lock机制可以加速软件包的安装。通过记录确切的版本信息,npm在安装过程中可以直接下载软件包的特定版本,而无需进行版本解析和查询。这提高了安装速度并减少了网络请求。
- 减少环境差异造成的问题:
在没有 package-lock机制时,不同的环境可能会因为 npm 的版本、软件包的新发布等原因导致不一致的依赖关系,进而引发一些问题。引入版本锁定可以有效减少这类问题。
总体而言,引入package-lock可以提高npm项目的稳定性、可重复性,以及确保团队协作和持续集成中的一致性。在开发实践中,也建议将package-lock.json文件纳入版本控制系统,以确保项目的所有开发者都使用相同的依赖版本。
例如,笔者的一个项目中,package-lock.json的文件内容,可能是这样的:
package-lock.json
{
"name": "ftest",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ftest",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"fastify": "^3.22.0",
"gm": "^1.23.1",
"jimp": "^0.16.1"
}
},
"node_modules/@babel/runtime": {
"version": "7.15.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz",
"integrity": "sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
...
另外需要注意,一般情况下,这个文件不需要,也不建议开发者进行手工的管理和编辑。它的修改,都是在npm的相关操作中自动进行的。
程序引用
在程序中,可以引用package.json中的项目,来实现一些运维管理甚至业务方面的功能。比如笔者经常使用package来获得版本号,展示在API或者页面中,方便运维工作中的故障排查。
引用package.json中的信息也非常简单,就是直接require就可以了:
js
const package = require("./package");
console.log(package.name,package.version);
小结
本文讨论了nodejs项目的核心配置文件,package.json相关的内容。这些内容包括package.json的基本结构,主要项目,并重点讨论了其中的依赖对象、脚本对象、依赖锁定等特性。笔者觉得,更好的了解和理解package.json和相关的管理机制,对于开发过程和开发项目的有效管理,是非常有帮助的。