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 react
、npm home react
即可快速访问。
script
为执行脚本,通过运行 npm run start,yarn start 即可,它还有一些内置的 script,但是很少用,本质是 npm 会建立一个shell 以及环境变量「会处理局部、全局 node_modules 的bin」。
添加变量
如环境变量,NODE_ENV=production <command>
,在执行命令时读取环境变量 NODE_ENV
为 production
。
注意:添加变量的方式,是和当前运行的环境有关系,比如以下的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 是支持的。
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 会将这些可执行文件链接到环境变量,使用户可以在命令行中直接运行这些命令。
- 当我们下载包的时候 npm i xxx
- 包管理工具会在 package.json 找到 bin 对象,将这个对象指向的文件软链到全局path「系统的可执行路径」
- 添加一个可执行权限「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
字段的一些常见用途:
- 解决依赖冲突:当多个依赖项要求使用不同版本的同一个软件包时,可能会出现依赖冲突。通过
resolution
字段,开发者可以指定要使用的特定版本,以解决冲突并确保依赖项能够正常工作。 - 锁定版本:
resolution
字段可以用于锁定依赖项的特定版本,以确保在后续安装或更新时不会自动升级到其他版本。这对于确保项目的稳定性和一致性非常有用。 - 版本范围管理:
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