首页 / Node.js 教程 / package.json 详解

Node.js 教程

package.json 详解

本教程共 76 篇 · 第 9 篇 · 更新于 2026-07-25 · 约 6 分钟阅读

Node.jspackage.jsonscriptsexports依赖

9. package.json 详解

本节目标:逐字段讲清 package.json,重点 scripts、exports、type 和依赖字段。

package.json 是 Node.js 项目的身份证。它告诉 npm 这个项目叫什么、依赖谁、怎么启动、兼容哪个 Node.js 版本。可以没有 README,但绝不能没有 package.json

这一章我们不浮于表面,把关键字段一个个拆开讲,顺带聊几个 v24 时代的新玩法。

基础字段:项目的名片

{
  "name": "my-awesome-app",
  "version": "1.0.0",
  "description": "一个做某事的 Node.js 应用",
  "main": "index.js",
  "type": "module",
  "license": "MIT"
}
  • name:包名,全小写,用连字符分隔。如果要发布到 npm,必须全局唯一。
  • version:遵循语义化版本(SemVer),格式 主版本.次版本.修订号
  • main:入口文件。别人 require('your-pkg')import 'your-pkg' 时,Node.js 会加载这个文件。
  • type"module" 表示默认按 ESM 解析 .js"commonjs" 或不写,就是传统的 CommonJS。
  • license:许可证。个人项目写 MIT 最省事,公司项目按法务要求来。
Tip

name 如果以 @ 开头,就是「作用域包(Scoped Package)」,比如 @company/private-tool。作用域包可以私有化,适合企业内部使用。

语义化版本:^ 和 ~ 的区别

版本号不是随便写的。1.2.3 三段分别代表:

位置含义什么时候变
1(MAJOR)主版本API 不兼容的破坏性变更
2(MINOR)次版本向后兼容的新功能
3(PATCH)修订号向后兼容的 Bug 修复

package.json 里常看到 ^~,它们控制更新的粒度:

{
  "dependencies": {
    "express": "^5.1.2",     // 允许 5.x.x,但不允许 6.0.0
    "lodash": "~4.17.21",    // 只允许 4.17.x
    "debug": "4.3.4"         // 精确锁定 4.3.4
  }
}
  • ^:锁定主版本,次版本和修订号可以升。最常用,平衡了稳定性和新特性。
  • ~:锁定到次版本,只允许修订号升级。更保守。
  • 无前缀:精确版本,一点都不许动。
Warning

生产环境的关键依赖,我建议精确写死版本号,或者至少用 ~^ 在升级时可能带入次版本的新功能,而新功能往往伴随着新 Bug。我有过凌晨被 ^ 自动升级搞崩服务的惨痛经历。

scripts 与生命周期钩子

scriptspackage.json 里最高频的字段。除了自定义命令,npm 还内置了几个特殊脚本:

命令对应的 script说明
npm startstart启动应用
npm testtest运行测试
npm restartrestart重启,默认顺序:stop → start
npm stopstop停止

其他脚本都要加 run,比如 npm run build

生命周期钩子是 scripts 的隐藏玩法。以 build 为例,npm 会自动找 prebuildpostbuild

{
  "scripts": {
    "prebuild": "npm run clean",
    "build": "node build.js",
    "postbuild": "npm run compress"
  }
}

执行 npm run build,实际跑的是 prebuildbuildpostbuild。这个约定省了你写冗长的命令链。

Note

prepare 是个特殊钩子,在 npm publish 之前和 npm install(不带参数)时自动执行。很多项目在这里放构建命令,确保发布的包是编译后的产物。

engines:划定 Node.js 版本边界

团队协作时,最怕有人用 Node.js v18 跑你的 v24 项目。engines 字段就是用来划定边界的:

{
  "engines": {
    "node": ">=20.0.0",
    "npm": ">=10.0.0"
  }
}

默认情况下,版本不匹配只会输出警告。如果希望强制报错,可以在项目根目录加 .npmrc

engine-strict=true

这样同事用错 Node.js 版本时,npm install 会直接失败,从源头上杜绝环境差异。

exports:现代包的入口地图

前面章节提过 exports,这里展开讲讲。它是 main 字段的升级版,能精确控制不同导入方式的入口:

{
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs",
      "default": "./dist/index.mjs"
    },
    "./helpers": {
      "import": "./dist/helpers.mjs",
      "require": "./dist/helpers.cjs"
    },
    "./package.json": "./package.json"
  }
}
  • "." 是主入口,对应 import 'your-pkg'
  • "./helpers" 是子路径入口,对应 import 'your-pkg/helpers'
  • "import""require" 分别服务 ESM 和 CommonJS。
  • "default" 是兜底,当前面的条件都不匹配时走这里。

exports 还有一个隐藏好处:它限制了外部能访问的文件。没有声明在 exports 里的路径,用户无法导入。这相当于给包加了访问控制,避免内部实现细节被外部依赖。

files:控制发布到 npm 的内容

默认情况下,npm publish 会把项目目录里几乎所有文件都上传。通常你只想发布编译产物和必要文档,源码、测试、配置文件不用带上去。

控制方式有两种:

1. files 白名单

{
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ]
}

只发布 dist 目录和两个文档。

2. .npmignore 黑名单

在项目根目录放 .npmignore,写法同 .gitignore

src/
tests/
.vscode/
*.log
Tip

如果同时有 .gitignore.npmignore,npm 会忽略 .gitignore,只认 .npmignore。如果你希望发布规则跟 Git 提交规则一致,可以建一个空的 .npmignore,这样 npm 就继续读 .gitignore 了。

node —run:v24 的脚本新姿势

Node.js v22.13+ / v24 LTS 引入了一个新命令:node --run(简写 node -r)。它的作用和 npm run 类似,但更快。

node --run build    # 等价于 npm run build

为什么更快?因为 node --run 跳过了 npm 的初始化过程,直接读取 package.json 里的 scripts 并执行。对于简单的脚本,启动速度能快几百毫秒。

不过它也有局限:不支持 pre/post 钩子,也不处理 npm 的环境变量注入。所以复杂流程还是老老实实 npm run,简单脚本可以尝鲜用 node --run

Note

node --run 自 v22.13.0(LTS)起稳定可用,v24 默认支持。如果你的项目要求兼容旧版本,还是用 npm run 更稳妥。

一个还算完整的 package.json 示例

把上面的知识点串起来,看一个实战配置:

{
  "name": "@myorg/api-server",
  "version": "2.1.0",
  "description": "内部 API 服务",
  "type": "module",
  "main": "./dist/index.cjs",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  },
  "files": ["dist"],
  "scripts": {
    "dev": "node --watch src/index.js",
    "build": "node build.js",
    "start": "node dist/index.mjs",
    "test": "node --test",
    "lint": "eslint src/"
  },
  "dependencies": {
    "express": "~5.1.0"
  },
  "devDependencies": {
    "eslint": "^9.0.0"
  },
  "engines": {
    "node": ">=22.0.0"
  },
  "license": "UNLICENSED",
  "private": true
}

注意最后的 "private": true,它阻止你 accidentally 执行 npm publish。内部项目一定要加,我听说过太多把私有代码发到公网 npm 的事故了。