你真的了解 package.json 吗?

大家好,我是刘布斯。

我们在前端项目或者 npm 中,都会看到package.json文件,但大家应该很少关注里面的东西。

其实 package.json 是前端开发中很基础,却又重要的一环,今天将带大家了解 package.json 的作用以及常用配置。

定义&作用

简单来说,package.json 是一个配置文件,并且通过记录项目的关键信息、依赖、脚本和配置,帮助开发者高效管理和维护项目。

展开一点讲,下面是它包含的数据,或者提供的能力:

  1. 项目元数据: 包含了关于项目的基本信息,如项目名称(name)、版本号(version)、描述(description)和作者(author)等。这些信息有助于识别和管理项目。

  2. 依赖管理: 记录了项目的所有依赖项,包括 dependencies(生产依赖)和 devDependencies(开发依赖)。通过这些信息,项目在安装时会自动下载和管理所需的库和工具。

  3. 脚本管理: scripts 字段允许你定义自定义的命令和脚本,例如启动服务器、运行测试、构建项目等。这些脚本可以通过 npm runyarn run 命令执行,简化了常见的开发任务。

  4. 版本控制: package.json 支持版本控制和语义化版本(SemVer),通过 version 字段和依赖项的版本范围来管理项目版本和依赖项的兼容性。

  5. 配置管理: 可以在 package.json 中配置各种工具和插件,例如 Babel、Webpack 和 ESLint。这使得项目的配置和依赖管理更加集中和一致。

  6. 项目和团队协作: 通过 package.json,团队成员可以确保在不同的开发环境中使用相同的依赖版本和配置,从而减少环境不一致带来的问题。

基本字段

package.json 文件包含了多个字段,每个字段都有其特定的用途和作用。

我们来介绍下常用的字段。

  • name: 包的名称,用于唯一标识包。名称通常应该是全局唯一的,并符合 npm 的命名规范。

  • version: 包的版本号,用于表示包的版本,遵循语义化版本控制(SemVer)。版本号格式通常为 major.minor.patch(主版本号.次版本号.补丁号)。

  • description: 对包的简要描述,介绍包的功能或目的,有助于用户了解包的用途。

  • main: 包的入口文件。指定模块的主要入口文件,通常是一个 JavaScript 文件。如果不设置则默认查找这个包根目录下的 index.js 文件。当我们使用 require 引入包的时候则会查找 main 对应路径的文件进行引入。这个文件应该遵循 CommonJS 模块规范,并且基于 ES5 规范编写。

  • module: npm 包的 ESM 规范的入口文件。这个文件可以使用 ES6import/export 语法,并支持 Tree Shaking 等特性。mainmodule 字段在 package.json 中共同存在时,分别用于指定不同模块规范下的入口文件,以满足不同环境和工具的需求。

  • scripts: 定义可执行的命令。通过 npm runyarn run 运行自定义脚本。例如,可以定义 starttestbuild 等脚本,用于启动开发服务器、运行测试或构建项目。

  • dependencies: 生产环境的依赖项。列出项目运行时所需的依赖包及其版本。npm install 安装这些依赖项。

  • devDependencies: 开发环境的依赖项。列出项目开发和测试时所需的依赖包。例如,构建工具、测试框架等。npm install --save-dev 安装这些依赖项。

  • peerDependencies: 同步依赖项。指定包的同行依赖(peer dependencies),这些依赖需要在宿主项目中安装,以确保兼容性。

  • engines:支持的 Node.js 版本或其他引擎版本。指定项目所需的 Node.js 版本范围或其他运行时环境版本,以确保在合适的环境中运行。

  • license:项目的许可证类型。规定项目的使用和分发许可,常见的许可证有 MIT、Apache-2.0 等。

  • repository:项目的版本控制仓库信息。指定项目的源代码仓库地址,以便用户和贡献者可以访问和协作。

  • browserslist:支持的浏览器范围。配置支持的浏览器版本范围,用于前端工具(如 Babel、Autoprefixer)生成兼容的代码。

还有一些可能没那么常用的,比如:

  • keywords: 关的关键字列表。提高包在 npm 上的可搜索性,帮助用户更容易找到包。

  • author: 包的作者信息。提供作者的名字和联系方式,有助于用户联系或了解包的维护者。

  • contributors: 贡献者列表。列出为项目做出贡献的其他开发者和其信息。

  • files: 包含在发布包中的文件。指定哪些文件和目录应包含在发布的包中,有助于减少包的大小。如果你只有少数不上传的文件,那也可以创建一个.npmignore 文件(类似于.gitignore,但该文件不会上传 npm),记录不需要上传的文件或目录,其余都上传。

  • bin: 可执行文件的映射。指定包中包含的可执行脚本及其对应的命令,便于全局安装时创建命令行工具。

{
  "name""my-tool",
  "version""1.0.1",
  "bin": {
    "my-tool""./bin/index.js"
  },
  "scripts": {
    "start""node index.js"
  }
  //...
}
  • config: 包的配置选项。提供自定义的配置项,供脚本或工具使用。

  • homepage: 目的主页 URL。

  • bugs: 提交问题的地址。

  • license: 的许可证(软件的开源协议)。

  • types: TypeScript 类型定义文件的路径。

  • exports:  定义包的子路径导出映射。

"exports": {
  ".": {
    "import""./index.esm.js"// 当使用 ESM 语法导入包时,解析到这个文件
    "require""./index.cjs" // 当使用 require() 导入包时,解析到这个文件
  },
  "./feature": {
    "import""./feature/esm/index.js"// 当导入特定的特性时,为 ESM 提供不同的入口点
    "require""./feature/cjs/index.js" // 当导入特定的特性时,为 CommonJS 提供不同的入口点
  }
},
  • unpkg: CDN 服务地址。

  • publishConfig: npm 包的发布指定特定的配置。该配置将覆盖全局或用户级别的 npm 配置。这对于需要将包发布到私有 npm 仓库或具有特殊发布需求的场景特别有用。

publishConfig 是一个对象包含以下属性:

registry:指定 npm 包的发布仓库地址。(私有 npm 仓库可以在这指定 url)

access:包的访问级别(默认 public 公开,restricted 私有)

tag:为发布的 npm 包指定一个标签。默认情况下 npm 包会使用 latest 标签发布,你可以手动指定为 beta

  • style: 该字段不是标准的 npm 字段,代表包中的样式文件路径。当 style 字段被设置时,某些工具(如 Webpack 的某些 loader 或构建脚本)可能会自动处理或包含这个样式文件。这通常用于那些希望将样式文件直接包含在其包中的前端库或组件。

  • sideEffects:用于告知打包工具(如 Webpack)哪些文件或模块在引入时具有副作用,从而影响 tree shaking 的行为。

在 package.json 中,sideEffects 字段是 Webpack v4 及更高版本引入的一个特性,用于标记项目中的某些文件或模块是否包含副作用(side effects)。这个字段对于 Webpack 的 tree shaking(树摇)优化至关重要。通过正确配置 sideEffects,打包工具可以更有效地移除未使用的代码,减小最终打包文件的大小。

版本号

版本号也是一块值得介绍的内容,大家了解的可能都是版本号遵循 semver 版本规范,通常由三位构成:x.y.z。分别对应主版本号、次版本号、修订号。

  1. 主版本号:该版本一般变化较大,可能不会兼容上个主版本的功能。
  2. 次版本号:在主版本的功能上进行更新,兼容同主版本的功能。
  3. 修订号:当修正了版本的 bug 之类

其实还有预发布版本以及构建元数据:

  • 预发布版本(Pre-release): 使用破折号(-)添加额外的标识,例如 1.0.0-alpha。表示该版本尚未正式发布,可能不稳定。

  • 构建元数据(Build Metadata):使用加号(+)添加,例如 1.0.0+20130313144700。提供附加的构建信息,不影响版本的排序或兼容性。

大家实践中,会发现很多依赖的版本号前面会有不同的符号,其实这是用于定义版本范围和兼容性。

常见的符号包括:

符号定义示例解释
^接受指定版本及其向后兼容的次版本号和补丁号更新,但不升级主版本号。^1.2.3兼容 1.2.31.x.x(小于 2.0.0
~接受指定版本及其向后兼容的补丁号更新,但不升级次版本号。~1.2.3兼容 1.2.31.2.x(小于 1.3.0
>=接受指定版本及其任何更高的版本。>=1.2.3兼容 1.2.3 及其更高版本
<=接受指定版本及其任何更低的版本。<=1.2.3兼容 1.2.3 及其更低版本
>接受指定版本及其任何更高版本(不包括指定版本)。>1.2.3兼容高于 1.2.3 的版本
<接受指定版本及其任何更低版本(不包括指定版本)。<1.2.3兼容低于 1.2.3 的版本
-版本范围。1.2.3 - 2.3.4兼容 1.2.32.3.4(包括这两个版本)
*任何版本。*兼容任何版本

处理版本冲突和依赖更新

在实际的开发中,大家可能会经常遇到这样的情况:

  • 项目中的不同包可能依赖于同一个库的不同版本。例如,A 依赖 lodash@4.17.19,而 B 依赖 lodash@4.17.21,这可能导致版本冲突。
  • 依赖的更新可能引入了不兼容的更改。如果一个库的更新破坏了向后兼容性,可能会导致依赖关系中的其他库出现问题。
  • 当某个包依赖的库又依赖其他库时,可能会出现多个版本的相同库,从而导致冲突。例如,库 C 可能需要 webpack@4.x,而库 D 需要 webpack@5.x
  • 如果项目没有明确的版本控制策略(如锁定版本号),依赖更新可能会引入不一致的版本。
  • 手动更改 package.json 中的版本号而没有充分测试可能导致意外的版本冲突。
  • ...

这样就会出现版本冲突的问题,我们一般可以手动编辑 package.json,调整为兼容的版本,并重新安装依赖。或者是使用 package-lock.jsonyarn.lock 文件锁定依赖版本,以确保在不同环境中使用相同的依赖版本。

在没有这些文件的项目中,首次使用 npm/yarn/pnpm 安装依赖的时候会自动生成。

当我们使用命令更新某个包的版本的时候,同时也会修改这些锁定的文件。

你真的都了解了吗?

上面介绍了这么多,再放一个大家常用的 element-plus 中的package.json,大家可以检测下,看是否能理解各个字段的含义:

{
  "name""element-plus",
  "version""2.3.12",
  "description""A Component Library for Vue 3",
  "keywords": [
    "element-plus",
    "element",
    "component library",
    "ui framework",
    "ui",
    "vue"
  ],
  "homepage""https://element-plus.org/",
  "bugs": {
    "url""https://github.com/element-plus/element-plus/issues"
  },
  "license""MIT",
  "main""lib/index.js",
  "module""es/index.mjs",
  "types""es/index.d.ts",
  "exports": {
    ".": {
      "types""./es/index.d.ts",
      "import""./es/index.mjs",
      "require""./lib/index.js"
    },
    "./es": {
      "types""./es/index.d.ts",
      "import""./es/index.mjs"
    },
    "./lib": {
      "types""./lib/index.d.ts",
      "require""./lib/index.js"
    },
    "./es/*.mjs": {
      "types""./es/*.d.ts",
      "import""./es/*.mjs"
    },
    "./es/*": {
      "types": ["./es/*.d.ts""./es/*/index.d.ts"],
      "import""./es/*.mjs"
    },
    "./lib/*.js": {
      "types""./lib/*.d.ts",
      "require""./lib/*.js"
    },
    "./lib/*": {
      "types": ["./lib/*.d.ts""./lib/*/index.d.ts"],
      "require""./lib/*.js"
    },
    "./*""./*"
  },
  "unpkg""dist/index.full.js",
  "jsdelivr""dist/index.full.js",
  "repository": {
    "type""git",
    "url""git+https://github.com/element-plus/element-plus.git"
  },
  "publishConfig": {
    "access""public"
  },
  "style""dist/index.css",
  "sideEffects": [
    "dist/*",
    "theme-chalk/**/*.css",
    "theme-chalk/src/**/*.scss",
    "es/components/*/style/*",
    "lib/components/*/style/*"
  ],
  "peerDependencies": {
    "vue""^3.2.0"
  },
  "dependencies": {
    "@ctrl/tinycolor""^3.4.1",
    "@element-plus/icons-vue""^2.0.6",
    "@floating-ui/dom""^1.0.1",
    "@popperjs/core""npm:@sxzz/popperjs-es@^2.11.7",
    "@types/lodash""^4.14.182",
    "@types/lodash-es""^4.17.6",
    "@vueuse/core""^9.1.0",
    "async-validator""^4.2.5",
    "dayjs""^1.11.3",
    "escape-html""^1.0.3",
    "lodash""^4.17.21",
    "lodash-es""^4.17.21",
    "lodash-unified""^1.0.2",
    "memoize-one""^6.0.0",
    "normalize-wheel-es""^1.2.0"
  },
  "devDependencies": {
    "@types/node""*",
    "csstype""^2.6.20",
    "vue""^3.2.37",
    "vue-router""^4.0.16"
  },
  "vetur": {
    "tags""tags.json",
    "attributes""attributes.json"
  },
  "web-types""web-types.json",
  "browserslist": ["> 1%""not ie 11""not op_mini all"],
  "gitHead""89d4ec863ce55fc3de2f0513631e76f695f8e791"
}

最后

还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。

有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。