要写好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
相关推荐
fishmemory7sec1 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆44 分钟前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
JUNAI_Strive_ving1 小时前
番茄小说逆向爬取
javascript·python
看到请催我学习1 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒2 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n03 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html