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
中获取。
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
,在执行命令时读取环境变量 NODE_ENV
为 production
。
注意:添加变量的方式,是和当前运行的环境有关系,比如以下的webpack 和 rollup 添加环境变量的方式不一样,具体实现可以参考示例和官网。以及 node 运行的时候也可以传参数的,在windows
系统中,无法通过此方式读取环境变量,需要借助工具 cross-env实现跨系统 。
{
"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 配置
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
{
"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 模块规范的环境。
没有默认值,
{
"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.
{
"main": "dist/index.js",
}
module
esm规范导出的模块,当使用 import xx from 'antd'
时,模块解析器会去优先找到定义在 package.json 文件中的 module 字段,并返回,如果没有定义会尝试 main 字段
{
"module": "dist/index.js",
}
browser
在浏览器环境中,用于指定替代模块的路径或文件。它的作用和 main 字段是一样的,并且语意更强,注意它在 node.js 中是不能用的
{
"browser": "dist/index.js",
}
exports
用于指定模块的导出方式和入口文件。通过定义 "exports" 字段,你可以为你的包提供灵活的模块导入方式,以适应不同的模块系统和环境需求。
"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 运行时或构建工具正确解析这些非标准路径。
// 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)上的入口文件路径。
"jsdelivr": "dist/axios.min.js",
"unpkg": "dist/axios.min.js",
bin
用于注册命令。npm 会将这些可执行文件链接到环境变量,使用户可以在命令行中直接运行这些命令。
// 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 中。
可选值,默认值["*"],默认所有文件都可发布,这样会有非必要的文件也会发布,浪费储存空间。
"files": [
"bin",
"build"
],
这样解压出来的文件,就只有 build 一个文件夹,以及其他的证书、CHANGELOG.md,package.json README.md
dependencies
dependencies
项目依赖:这些依赖都会成为线上生产环境中的代码组成部分。当它依赖的 npm 包被下载时,dependencies 下的模块也会作为依赖,一起被下载。
npm add the-answer // --save
yarn add the-answer
pnpm add the-answer
devDependencies
开发依赖:一般是只用于开发环境的开发库。当引用线上库的时候,devDependencies 不会被自动下载,因为它对于使用者来说是不需要的。
注意:并不是只有在 dependencies 中的模块才会被一起打包,而在 devDependencies 中的依赖一定不会被打包。实际上,依赖是否被打包,完全取决于项目里是否被引入了该模块。dependencies 和 devDependencies 在业务中更多的只是一个规范作用,我们自己的应用项目中,使用 npm install 命令安装依赖时,dependencies 和 devDependencies 内容都会被下载。
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 中配置:
"peerDependencies": {
"React": "^17.0.0"
}
插件不能单独运行,插件正确运行的前提是核心依赖库必须先下载安装。我们不希望核心依赖库被重复下载,在项目中,同一插件体系下,核心依赖库版本最好相同
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" 的元数据提供了对该包的描述信息。
{
"peerDependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"description": "DOM rendering for React"
}
}
}
bundledDependencies
指定需要捆绑(bundle)到你的包中的依赖项,会增加包的大小,因此选择那些必要的、无法通过用户自行安装的依赖项
通常默认情况下,依赖项不会被包含在你的包中。而是期望用户在安装你的包时自行安装这些依赖项。然而,有时你可能希望将特定的依赖项直接捆绑(bundle)到你的包中,以便用户在安装你的包时无需单独安装这些依赖项。
// 当执行 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」优化处理,可以告诉它这个包是否有副作用。
"sideEffects": true // 表示有副作用
"sideEffects": false // 表示没有副作用
"sideEffects": [] // 表示该路径下的文件是有副作用的,不用 tree shaking,其他模块是没有副作用的,但是具体实际情况以打包工具的实际逻辑为准。
engines
指定项目 node 版本 和 npm、yarn、pnpm 版本
注意 npm 要配置 npm config set engine-strict=true,或者在 .npmrc 文件中配置
"engines": {
"node": ">=12",
"npm": ">=6",
"yarn":">2",
"pnpm":"