要写好npm包,Package.json 是需要掌握的

Package 知识体系

熟练掌握 Package.json 各个字段的意思以及用途,对于开发 npm 包和日常开发将如虎添翼。希望阅读本文后你可以解决以下问题。

  • ^1.2.3 和 ~1.2.3 版本范围是什么
  • 如何防止业务项目被发布到 npm 仓库
  • 当 npm 包版本出现冲突了,应该如何解决
  • 如果限制团队成员使用相同的 node 和包管理工具「npm、yarn、pnpm」,解决方案是多样的
  • Vite、Webpack 加载模块优先级是什么,注意:webpack 的优先级是可以配置的
  • 你可以利用 pre、post 钩子做些什么什么sao操作?
  • 如何开发一个 script 脚本

知识获取网站 rushjs.io/zh-cn/

官方文档 :docs.npmjs.com/cli/v9/conf...,以下的这些都配置都可以通过 process.env 中获取。

vbnet 复制代码
npm_package_name: 'my_package_name',
npm_package_scripts_start: 'node ./index.js',
​
格式: npm_package_ + package.json 中的 key值。

name

为项目的名称,它需要遵循 npm 包名规范

  • 如果是一个 npm 包,这个字段是必填的,并具备唯一性。
  • 否则,非必填。

version

为版本号,它需要遵循 Semantic Versioning 规范

  • 如果是一个 npm 包,这个字段是必填的,每次发版本需要改变版本号。
  • 否则,非必填。

homepage

官网,npm docs reactnpm home react 即可快速访问。

script

为执行脚本,通过运行 npm run start,yarn start 即可,它还有一些内置的 script,但是很少用,本质是 npm 会建立一个shell 以及环境变量「会处理局部、全局 node_modules 的bin」。

添加变量

如环境变量,NODE_ENV=production <command> ,在执行命令时读取环境变量 NODE_ENVproduction

注意:添加变量的方式,是和当前运行的环境有关系,比如以下的webpack 和 rollup 添加环境变量的方式不一样,具体实现可以参考示例和官网。以及 node 运行的时候也可以传参数的,在windows 系统中,无法通过此方式读取环境变量,需要借助工具 cross-env实现跨系统 。

perl 复制代码
{
  "scripts": {
    "start": "node server.js myDevelopment myPrams", // 通过 process.argv 访问如下
     // argv: [ 
     //  '/usr/local/bin/node', // Node.js 的执行路径
     //  '~/Desktop/myDemo/server', // 脚本文件的路径
     //  'myDevelopment', // 参数1
     //  'myPrams'  // 参数2
     // ],
    // 通过 `process.env.NODE_ENV`
    "dev": "NODE_ENV=production webpack",
    "dev:cross-platform": "cross-env NODE_ENV=production webpack", 
    "build": "rollup --config rollup.config.js --environment NODE_ENV:production --bundleConfigAsCjs", 
    "build": "webpack",
    "test": "mocha tests/*.js",
    "lint": "eslint src/*.js",
​
  }
}
​

pre/post 钩子:允许执行脚本前的前后执行,可以用于执行一些预处理或后处理任务

当我们在手动执行 npm run xxx 时,如果 prexxx postxxx scripts 存在时,它将会自动执行 npm run prexxx 以及 npm run postxxx

npm yarn1.x 是支持的。

pnpm 需要设置npm 配置

arduino 复制代码
npm config set enable-pre-post-scripts=true
还有一些默认的钩子是有问题的,比如 preinstall postinstall。 它们都会在脚本结束后一起执行
自定义的钩子执行逻辑是符合预期的

注意:pre/post 的安全问题,黑客可以通过修改package.json 中的script 字段的 preinstall 和 postinstall 这样可以执行一些脚本,带来安全性问题。

如何自己开发一个 script

可以参考去实现 github.com/kentcdodds/...kentcdodds.com/blog/tools-...

repository

为仓库地址,string 或 object

json 复制代码
{
  "repository": {
    "type": "git",
    "url": "https://github.com/facebook/react.git"
  }
}
​
{
  "repository": "https://github.com/facebook/react.git",
}

type

用于指定模块的类型。

  • "module":表示该包是一个 ES 模块,适用于支持 ES 模块规范的环境。
  • "commonjs":表示该包是一个 CommonJS 模块,适用于 Node.js 或不支持 ES 模块规范的环境。

没有默认值,

json 复制代码
{
  "type": "module"
}

注意:"type" 字段的使用是为了提供更准确的模块类型信息,以帮助使用者根据不同的环境选择正确的模块导入方式。但这并不影响实际的模块导入和包的使用,因为最终还是取决于具体的模块加载器和环境支持。

在缺少 "type" 字段的情况下,模块的类型将根据具体的上下文和环境进行推断和处理

文件后缀也可以定义模块规范,demo.cjs、demo.ejs

模块规范

参考模块查找解析规范,但具体的实现规则根据的打包器又不一样。

Node.js

imports「私有映射」 > exports「require > default , 找不到模块报错」> main ,其他的模块也不会去找了。

Webpack

  • 在 Web 浏览器环境配置下,优先选择:exports > browser > module > main

注意:在 webpack 中需要配置"type": "module" 才能生效,webpack 4 和5 还有一定的区别,4 只会查 import,并不会忽略其他配置,webpack5和vite是类似的 参考

Vite

  • 在 Web 浏览器环境配置下,exports「import >browser > default ,找不到模块报错,因为require不加载」 > module > browser > main

在 Vite 中 配置了exports 字段就会忽略其他的配置,找不到就会报错。

注意:exports 中 的 default 最好是 cjs 和 esm 都兼容的

总的来说 exports 优先级高,配置后就会忽略其他的配置,其中webpack 和 vite 实现略有不同,如果把browser字段去掉,规律就是 module > main

main

cjs规范导出的模块,当使用 const foo = require("foo"),模块解析器会去优先找到定义在 package.json 文件中的 main 字段,并返回。

If main is not set, it defaults to index.js in the package's root folder.

css 复制代码
{
  "main": "dist/index.js",
}

module

esm规范导出的模块,当使用 import xx from 'antd' 时,模块解析器会去优先找到定义在 package.json 文件中的 module 字段,并返回,如果没有定义会尝试 main 字段

json 复制代码
{
  "module": "dist/index.js",
}

browser

在浏览器环境中,用于指定替代模块的路径或文件。它的作用和 main 字段是一样的,并且语意更强,注意它在 node.js 中是不能用的

json 复制代码
{
  "browser": "dist/index.js",
}

exports

用于指定模块的导出方式和入口文件。通过定义 "exports" 字段,你可以为你的包提供灵活的模块导入方式,以适应不同的模块系统和环境需求。

javascript 复制代码
"exports": {
  "require": "./index.js",
  "import": "./index.mjs",
  "./style": "./dist/css/index.css'
 }
}
​
// 以前的使用方式 import `packageA/dist/css/index.css`;
// 现在的使用方式 import `packageA/style`;
​
也可以使用这种方式
 "." 表示默认导出方式。
require 代表相应环境的 cjs 规范,import 代表 esm,default就是默认。
 "exports": {
    ".": {
      "default": {
        "import": "./dist/index-exports.esm",
        "browser": "./dist/index-browser-exports.js",
        "require": "./dist/index-exports-require.js",
        "default": "./dist/index-exports-default.js"
      }
    }
  },
​

注意:npm v7、yarn v2 才可以使用,它的优先级最高。

imports

用于指定 ECMAScript 模块的导入映射的配置。它用于告诉 JavaScript 运行时或构建工具如何解析模块的导入路径。 JavaScript 解析模块将会从 imports 中查找模块映射,进而导入成功。

注意:它是写在项目的 package.json 下面的,由项目开发者决定需要怎么查找模块「私有映射」,去映射到那个包,imports 是优先级最高的

在以下场景中特别有用:

  • 解决模块路径的别名:可以使用 "imports" 字段来为常用模块路径或目录路径指定别名,以简化导入语句。例如,将较长的路径映射为短的别名路径,或者将相对路径映射为绝对路径别名。
  • 处理非标准模块路径:有时,某些模块可能具有非标准的导入路径。通过在 "imports" 字段中定义相应的映射,可以让 JavaScript 运行时或构建工具正确解析这些非标准路径。
javascript 复制代码
// demo
{
  "dependencies": {
    "rollup-package-demo-juice": "^1.0.4"
  },
  "imports": {
    "#rollup-package-demo-juice": {
      "import":"rollup-package-demo-juice",
      "require":"rollup-package-demo-juice",
      "node": "rollup-package-demo-juice",
      "default": "rollup-package-demo-juice"
    }}
  
}
​
开源的用法
https://www.npmjs.com/package/esbuild-loader?activeTab=code
"imports": {
  "#esbuild-loader": {
    "types": "./src/index.d.ts",
    "development": "./src/index.ts",
    "default": "./dist/index.cjs"
  }
},
​
使用「未验证」 
import typesModule from '#esbuild-loader/types';
import developmentModule from '#esbuild-loader/development';
import defaultModule from '#esbuild-loader/default';

注意:目前这个模块是和 webpack 绑定的,并且 imports 字段。

由 ECMAScript 提案 Import Conditions 引入的。这个提案旨在为 ECMAScript 模块系统提供一种配置方式,以解决模块导入路径的问题。

提案的第一版草案(Stage 1)于 2020 年 7 月提出,并在接下来的 TC39 会议上进行了讨论和改进。然后,在 2021 年 9 月的 TC39 会议上,该提案进入了第二版草案(Stage 2)。

unpkg、jsdelivr

用于指定在 unpkg、jsdelivr(一个用于在浏览器中直接引入 npm 包的 CDN)上的入口文件路径。

json 复制代码
"jsdelivr": "dist/axios.min.js",
"unpkg": "dist/axios.min.js",

bin

用于注册命令。npm 会将这些可执行文件链接到环境变量,使用户可以在命令行中直接运行这些命令。

  1. 当我们下载包的时候 npm i xxx
  2. 包管理工具会在 package.json 找到 bin 对象,将这个对象指向的文件软链到全局path「系统的可执行路径」
  3. 添加一个可执行权限「x」
javascript 复制代码
// package.json
"bin":{
    "mybin":"./binbin/index.js"
}
​
​
bin/index.js  // 脚本文件
#!/usr/bin/env node  // 解释器声明
​
console.log( '执行' )
​
​
// 将会出现两种情况,可以通过 npm link 调式
​
1.局部安装执行方式 node node_modules/.bin/mybin
2.全局安装执行方式 mybin

注意配合 npx 使用会更方便,yarn link 是不可以的,因为bin文件没有链接处理,node_modules 中的 .bin 文件没有生成

files

这个字段允许你控制在使用 npm publish 命令发布你的包时,哪些文件将被包含在发布的 tarball 中。

可选值,默认值["*"],默认所有文件都可发布,这样会有非必要的文件也会发布,浪费储存空间。

go 复制代码
"files": [
  "bin",
  "build"
],
​
这样解压出来的文件,就只有 build 一个文件夹,以及其他的证书、CHANGELOG.md,package.json README.md

dependencies

dependencies

项目依赖:这些依赖都会成为线上生产环境中的代码组成部分。当它依赖的 npm 包被下载时,dependencies 下的模块也会作为依赖,一起被下载。

csharp 复制代码
npm add the-answer // --save
yarn add the-answer
pnpm add the-answer

devDependencies

开发依赖:一般是只用于开发环境的开发库。当引用线上库的时候,devDependencies 不会被自动下载,因为它对于使用者来说是不需要的。

注意:并不是只有在 dependencies 中的模块才会被一起打包,而在 devDependencies 中的依赖一定不会被打包。实际上,依赖是否被打包,完全取决于项目里是否被引入了该模块。dependencies 和 devDependencies 在业务中更多的只是一个规范作用,我们自己的应用项目中,使用 npm install 命令安装依赖时,dependencies 和 devDependencies 内容都会被下载。

csharp 复制代码
npm add the-answer -D // --dev-save 
yarn add the-answer -D 
pnpm add the-answer -D 

peerDependencies

它确保使用者在安装你的包时也安装了指定的依赖包,并在不一致的依赖环境下提供警告或错误信息。它的作用是让开发者和包的开发者建立一种合作和依赖的关系

  • 避免重复安装,因为这个库很经常被安装,比如 react 项目,引用了一个 antd,antd 就配置了 peerDependencies
  • 框架或库的插件,由于是强依赖,所以通常是可以使用 peerDependencies 去解决重复安装的,比如 babel monorepo 体系下面的 babel-preset-env

举个例子,假设 react-ui@1.2.2 只提供一套基于 React 的 UI 组件库,它需要宿主环境提供指定的 React 版本来搭配使用,因此我们需要在 React-ui 的 package.json 中配置:

json 复制代码
"peerDependencies": {
    "React": "^17.0.0"
}

插件不能单独运行,插件正确运行的前提是核心依赖库必须先下载安装。我们不希望核心依赖库被重复下载,在项目中,同一插件体系下,核心依赖库版本最好相同

sql 复制代码
npm --no-save the-answer
yarn add --peer the-answer
pnpm add --peer the-answer

注意:npm versions 3-6 和 yarn 中的 peerDependencies 不会自动下载,只会提示警告,v7 将会默认自动下载。

peerDependenciesMeta

对 peerDependencies 中的包更多的描述信息,帮助包的使用者更好地了解和处理 peerDependencies。

以下 "react" 的元数据指定了该对等依赖是可选的,而"react-dom" 的元数据提供了对该包的描述信息。

json 复制代码
{
  "peerDependencies": {
    "react": "^17.0.0",
    "react-dom": "^17.0.0"
  },
  "peerDependenciesMeta": {
    "react": {
      "optional": true
    },
    "react-dom": {
      "description": "DOM rendering for React"
    }
  }
}

bundledDependencies

指定需要捆绑(bundle)到你的包中的依赖项,会增加包的大小,因此选择那些必要的、无法通过用户自行安装的依赖项

通常默认情况下,依赖项不会被包含在你的包中。而是期望用户在安装你的包时自行安装这些依赖项。然而,有时你可能希望将特定的依赖项直接捆绑(bundle)到你的包中,以便用户在安装你的包时无需单独安装这些依赖项。

perl 复制代码
// 当执行 npm pack 时,会产生 my-package-1.0.0.tgz,你的包将捆绑(bundle) "lodash" 和 "axios" 两个依赖项。这意味着当用户安装你的包时,这些依赖项会作为你的包的一部分直接被安装,而不需要用户手动安装它们。
{
  "name":"my-package",
   "version": "1.0.0",
  "bundledDependencies": ["lodash", "axios"]
}

注意:在 bundledDependencies 中指定的依赖包,必须先在 dependencies 和 devDependencies 声明过,否则在 npm pack 阶段会进行报错。

optionalDependencies

可选依赖,即使对应依赖项安装失败了,也不会影响整个安装过程。

resolution

用于指定解决依赖项冲突或版本选择的规则和策略。

当一个项目具有多个依赖项,而这些依赖项之间存在版本冲突或不兼容时,resolution 字段可以帮助开发者指定优先使用的版本,或者通过特定规则解决依赖冲突。

以下是 resolution 字段的一些常见用途:

  1. 解决依赖冲突:当多个依赖项要求使用不同版本的同一个软件包时,可能会出现依赖冲突。通过 resolution 字段,开发者可以指定要使用的特定版本,以解决冲突并确保依赖项能够正常工作。
  2. 锁定版本:resolution 字段可以用于锁定依赖项的特定版本,以确保在后续安装或更新时不会自动升级到其他版本。这对于确保项目的稳定性和一致性非常有用。
  3. 版本范围管理:resolution 字段还可以用于管理依赖项的版本范围。通过指定特定的版本范围,可以确保在安装或更新依赖项时仅选择符合规定范围的版本。

注意:它并不是 npm 官方的标准字段,所以只有 yarn 或 pnpm 是支持解析这个字段的

sideEffects

用于给打包工具「Webpack」优化处理,可以告诉它这个包是否有副作用。

json 复制代码
"sideEffects": true // 表示有副作用
"sideEffects": false // 表示没有副作用
"sideEffects": [<模块路径列表>] // 表示该路径下的文件是有副作用的,不用 tree shaking,其他模块是没有副作用的,但是具体实际情况以打包工具的实际逻辑为准。

engines

指定项目 node 版本 和 npm、yarn、pnpm 版本

注意 npm 要配置 npm config set engine-strict=true,或者在 .npmrc 文件中配置

json 复制代码
"engines": {
  "node": ">=12",
  "npm": ">=6",
  "yarn":">2",
  "pnpm":"<6"
},

限制不同的包管理包工具混用,可以使用 preinstall 执行脚本限制

arduino 复制代码
if (!process.env['npm_config_user_agent'].startsWith('pnpm')) {
  console.error(`
////////////////////////////////////////////////////////////
     
    告别 yarn,请使用速度更快、体积更小的 pnpm
    pnpm 全局安装: `npm install -g pnpm `
    pnpm 安装项目依赖: `pnpm install`
 
////////////////////////////////////////////////////////////
  `);
  process.exit(-1);
}

private

定义 npm 包发布行为

json 复制代码
"private": true // npm 将会拒绝发布到 npm 源上,可以在 publishConfig 指定可以发布的源

publishConfig

指定发布包时的配置选项,通过定义这些选项,你可以控制发布过程中的行为和设置,如自定义注册表 URL、访问级别、标签等。这样可以满足你的发布需求并提供灵活性和定制化。

json 复制代码
{
  "publishConfig": {
    "registry": "https://custom-registry.example.com", // 自定义的源
    "access": "restricted",
  }
}

typings、types

ts 全局声明文件,这两个字段的作用是一样的。

"typings" 字段是在较早版本的 npm 中使用的

"types" 字段是在 npm 5.0.0 及以上版本

json 复制代码
"typings": "./index.d.ts",
"types": "./index.d.ts",

license

为开源协议,太多协议了,看图。

其他不重要字段

author:作者,限定一个,同时也可以写对象

perl 复制代码
author:{
  "name" : "Barney Rubble",
  "email" : "b@rubble.com",
  "url" : "http://barnyrubble.tumblr.com/"
}
​
也可以缩写
"author": "Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)"

contributors:贡献者,是一个数组,可以多个

description:描述包的信息,增加曝光率

keywords:描述包的关键字,增加曝光率

homepage:官网

bugs: 上报bug,通过github issues,或者通过email

perl 复制代码
{
  "url" : "https://github.com/owner/project/issues",
  "email" : "project@hostname.com"
}

funding:捐助

man: string、array,它们表示命令行工具的帮助文档文件的路径或文件名。这些文件通常使用 Unix 风格的 man 页面格式,扩展名为 .1.2.3 等。这些文件可以在安装包时被链接到系统的 man 页面路径中,以便用户可以通过 man 命令查看和浏览帮助文档。

directories:只是提供了一种约定,用于指定包中不同类型文件的目录位置。这些路径的实际使用和处理可能取决于你的构建工具、打包配置或其他工作流程。

perl 复制代码
{
  "directories": {
    "lib": "src", // 源代码位于 "src" 目录
    "bin": "bin", // 可执行文件位于 "bin" 目录
    "man": "man", // man 页面位于 "man" 目录
    "doc": "docs", // 文档文件所在 "docs" 目录
    "example": "examples", // 示例文件 "examples" 目录。
    "test": "test" // 测试文件 "test" 目录
  }
}

config:配置信息,使用场景较少,要用也是有其他方式代替的。

arduino 复制代码
定义:
{
  "config": {
    "apiEndpoint": "https://api.example.com",
    "maxRetries": 3
  }
}
​
使用:
const apiEndpoint = process.env.npm_package_config_apiEndpoint;
const maxRetries = process.env.npm_package_config_maxRetries;
console.log(apiEndpoint); // 输出: https://api.example.com
console.log(maxRetries); // 输出: 3
修改:
npm config set your-package-name:apiEndpoint "https://new-api.example.com"
​

overrides:用于覆盖或扩展其他包的配置

workspaces

定义工作区(workspaces)配置,它是 npm 的一项功能,用于管理包含多个子包(或称为工作区)的项目。

工作区是一种将多个相关的包组织在一个父项目下的方式。这对于具有多个包、共享代码或依赖关系的项目非常有用。

"workspaces" 字段是一个数组,其中的每个元素表示一个工作区路径,可以是相对路径或绝对路径。

注意:"workspaces" 功能要求使用 npm 7 或更高版本,并且只在具有多个子包的项目中才有意义。

json 复制代码
{
  "workspaces": [
    "packages/*"
  ]
}
​

npm 版本规范 (semantic version)

dependencies 中的版本管理是使用 semantic version,具体参考 :semver.org/lang/zh-CN/

{major} . {minor} . {patch} - {fix} {主版本号} . {次版本号} . {修补版本号} - {非正式版版本号}

php 复制代码
"dependencies": {
    "typescript": "^4.0.5-0",
}
// 主版本号: 4
// 次版本号: 0
// 修补版本号: 5
// 非正式版版本号: 0
​
version Must match version exactly
>version Must be greater than version
>=version etc
<version
<=version
~version "Approximately equivalent to version" See semver
^version "Compatible with version" See semver
1.2.x 1.2.0, 1.2.1, etc., but not 1.3.0
http://... See 'URLs as Dependencies' below
* Matches any version
"" (just an empty string) Same as *
version1 - version2 Same as >=version1 <=version2.
range1 || range2 Passes if either range1 or range2 are satisfied.
git... See 'Git URLs as Dependencies' below
user/repo See 'GitHub URLs' below
tag A specific version tagged and published as tag See npm dist-tag
path/path/path See Local Paths below
相关推荐
90后的晨仔7 分钟前
在macOS上无缝整合:为Claude Code配置魔搭社区免费API完全指南
前端
沿着路走到底37 分钟前
JS事件循环
java·前端·javascript
子春一21 小时前
Flutter 2025 可访问性(Accessibility)工程体系:从合规达标到包容设计,打造人人可用的数字产品
前端·javascript·flutter
白兰地空瓶1 小时前
别再只会调 API 了!LangChain.js 才是前端 AI 工程化的真正起点
前端·langchain
jlspcsdn2 小时前
20251222项目练习
前端·javascript·html
行走的陀螺仪2 小时前
Sass 详细指南
前端·css·rust·sass
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
React 怎么区分导入的是组件还是函数,或者是对象
前端·react.js·前端框架
LYFlied3 小时前
【每日算法】LeetCode 136. 只出现一次的数字
前端·算法·leetcode·面试·职场和发展
子春一23 小时前
Flutter 2025 国际化与本地化工程体系:从多语言支持到文化适配,打造真正全球化的应用
前端·flutter
QT 小鲜肉3 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记