npm知识
npm 是什么
npm 为你和你的团队打开了连接整个 JavaScript 天才世界的一扇大门。它是世界上最大的软件注册表,每星期大约有 30 亿次的下载量,包含超过 600000 个包(package)(即,代码模块)。来自各大洲的开源软件开发者使用 npm 互相分享和借鉴。包的结构使您能够轻松跟踪依赖项和版本。
npm 由三个独立的部分组成:
- 网站 —— 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。
- 注册表(registry) —— 是一个巨大的数据库,保存了每个包(package)的信息。
- 命令行工具 (CLI) —— 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。
公共 npm 注册表
公共 npm 注册表是一个 JavaScript 包数据库,每个包都由软件和元数据组成。开源开发人员和公司的开发人员使用 npm 注册表向整个社区或其组织成员贡献包,并下载包以在他们自己的项目中使用。
要开始使用注册表,请注册一个 npm 帐户并查看“入门”和 CLI 文档。
包和模块
npm 注册表包含包,其中许多也是 Node 模块,或者包含 Node 模块。请继续阅读以了解它们有何不同以及它们如何相互作用。
包
包是由 package.json
文件描述的文件或目录。包必须包含 package.json
文件才能发布到 npm 注册表。包可以不受限制,也可以限定为用户或组织,范围限定的包可以是私有或公共的。
包的格式
- a) 包含由
package.json
文件描述的程序的文件夹。 - b) 一个包含(a)的 gzip 压缩包。
- c) 解析为(b)的 URL。
- d) 在注册表中发布(c)的
<name>@<version>
。 - e) 指向(d)的
<name>@<tag>
。 - f) 具有满足(e)
的最新
标记的<name>
。 - g) 一个
git
url,克隆后会导致(a)。
模块
模块是 node_modules
目录中可由 Node.js require()
函数加载的任何文件或目录。
要通过 Node.js require()
函数加载,模块必须是以下之一:
- 一个文件夹,其中包含一个
package.json
文件,其中包含“main”
字段。 - 一个 JavaScript 文件。
**注意:**由于模块不需要具有
package.json
文件,因此并非所有模块都是包。只有具有package.json
文件的模块也是包。
scope
所有 npm 包都有一个名称。一些包名称也有范围(作用域)。作用域遵循包名称的通常规则(URL安全字符,没有前导点或下划线)。在包名称中使用时,作用域前面有一个 @
符号,后面有一个斜线,例如:
@somescope/somepackagename
作用域 (Scope) 是一种将相关包组合在一起的方法,也会影响 npm 处理包的方式。
每个 npm 用户/组织都有自己的范围,只有你才能在你的范围中添加包。这意味着您不必担心有人会抢在您之前使用您的软件包名称。因此,这也是向组织发送官方软件包的好方法。
范围包可以从 npm@2
开始发布和安装,并且受主 npm 注册表支持。无范围包可以依赖于 scoped 包,反之亦然。npm 客户端向后兼容无范围注册表,因此它可以用于同时处理有范围和无范围注册表。
安装范围包
作用域包安装到常规安装文件夹的子文件夹中,例如,如果普通包安装在 node_modules/packagename
中,则作用域包将安装在 node_modules/@myorg/packagename
中。范围文件夹 (@myorg
) 只是范围的名称,前面有一个 @
符号,可以包含任意数量的范围包。
通过在 npm install 中按名称引用 @ 符号来安装范围包:
npm install @myorg/mypackage
或者用 package.json
:
"dependencies": {
"@myorg/mypackage": "^1.3.0"
}
请注意,如果省略
@
符号,则在任何一种情况下,npm 都将尝试从 GitHub 安装。
导入范围包
由于范围包安装在 scope 文件夹中,因此在代码中需要它们时,您必须包含范围的名称,例如:
require("@myorg/mypackage");
Node 处理范围文件夹的方式并没有什么特别之处。这只需要名为 @myorg
的文件夹中的 mypackage
模块。
发布范围包
范围包可以从 npm 2.0 或更高版本中发布,并且可以发布到支持它们的任何注册表,包括主 npm 注册表。
将公共范围的包发布到主 npm 注册表
发布到范围时,您有两个选项:
- 发布到您的用户范围(示例:
@username/模块
) - 发布到组织范围(示例:
@org/module
)
如果要将公共模块发布到组织范围,则必须首先使用要发布到的范围名称创建组织,或者将其添加到具有适当权限的现有组织。例如,如果要发布到 @org
,则需要在尝试发布之前在 npmjs.com 上创建 org
组织。
默认情况下,范围包不是公共的。您需要使用初始 npm publish
命令指定 --access public
。这将发布包并将访问权限设置为 public
,就像您在发布后运行 npm access public
一样。在发布现有范围包的新版本时,您无需执行此操作。
将私有范围的包发布到 npm 注册表
要将私有范围的包发布到 npm 注册表,您必须拥有 npm 私有模块帐户。
然后,您可以使用 npm publish
或 npm publish --access restricted
发布模块,它将出现在 npm 注册表中,但访问权限受限。然后,如果需要,您可以使用 npm access
或在 npmjs.com 网站上更改访问权限。
将范围与注册表相关联
范围可以与单独的注册表相关联。这样,您就可以无缝地混合使用来自主 npm 注册表和一个或多个私有注册表的包,例如 GitHub Packages 或开源 Verdaccio 项目。
您可以在登录时将范围与注册表相关联,例如
npm login --registry=http://reg.example.com --scope=@myco
范围与 registry 具有多对一的关系:一个 registry 可以托管多个 scope,但一个 scope 只指向一个 registry。
您还可以使用 npm config
将范围与注册表关联:
npm config set @myco:registry=http://reg.example.com
一旦范围与注册表关联,具有该范围的包的任何 npm install
都将改为从该注册表请求包。包含范围的包名称的任何 npm publish
都将发布到该注册表。
npm install 原理
-
首先得有
package.json
包管理文件才可以执行npm install
-
npm install -> 看是否有
package-lock.json
文件 -
没有
package-lock.json
文件,则构建依赖关系,然后从registry
仓库下载安装包(压缩文件),将压缩包添加到本地的缓存文件中再将下载的压缩包解压到node_modules
中,同时生成package-lock.json
文件,完成安装 -
有
package-lock.json
文件,则先检查下载的包和package-lock.json
文件中的包版本是否符合semver
版本规范,不一定非要完全一样,像 2.2.1 和 2.2.2 是一样的 -
不一致,则重新构建依赖关系,然后就是和第 3 步一样,去仓库下载,加压,添加缓存,解压到
node_modules
中,生成新的package-lock.json
-
一致,则会去本地缓存文件中去查找缓存文件,找到缓存文件,将压缩包解压到
node_modules
中,完成安装
package.json 文件
package.json 文件是项目的清单,用于存储与项目有关的元数据以及依赖项包列表。它向 npm 提供信息,使其能够识别项目并处理所有项目的依赖项。通过 package.json 文件,其他人可以轻松安装和管理与项目相关的软件包。
package.json 文件必须是实际的 JSON,而不仅仅是 JavaScript 对象文本。
name
如果您计划发布包,则 package.json 中最重要的内容是 name
和 version
字段,因为它们是必需的。名称和版本共同构成一个标识符,该标识符假定为完全唯一。对包的更改应随版本更改一起进行。如果您不打算发布包,则 name
(名称) 和 version
(版本) 字段是可选的。
一些规则:
- 名称必须小于或等于 214 个字符。这包括 scoped 包的范围。
- 作用域包的名称可以以点或下划线开头。如果没有范围,则不允许这样做。
- 包的名称中不能包含大写字母。
- 名称最终成为 URL、命令行上的参数和文件夹名称的一部分。因此,名称不能包含任何非 URL 安全字符。
一些提示:
- 不要使用与核心 Node 模块相同的名称。
- 不要在名称中添加 “js” 或 “node” 。假设它是 js,因为你正在编写一个 package.json 文件,你可以使用
engines
字段指定引擎。 - 该名称可能会作为参数传递给
require()
,因此它应该简短,但也具有合理的描述性。 - 在你开发依赖之前,你可能要检查 npm 注册表,看看是否已经有这个名字。
- 名称可以选择以范围为前缀,例如
@myorg/mypackage
。
version
如果您计划发布包,则 package.json 中最重要的内容是 name
和 version
字段,因为它们是必需的。名称和版本共同构成一个标识符,该标识符假定为完全唯一。对包的更改应随版本更改一起进行。如果您不打算发布包,则 name
(名称) 和 version
(版本) 字段是可选的。
版本必须可由 node-semver
解析,它作为依赖项与 npm 捆绑在一起。(npm install semver
自己使用它)
description
在其中添加描述。它是一个字符串。这有助于人们发现你的包,因为它在 npm search
中列出。
keywords
在其中放置关键字。它是一个字符串数组。这有助于人们发现你的包,因为它在 npm search
中列出。
author
顾名思义就是作者,表示该项目包的作者。它有两种形式,一种是字符串格式:
{
"author": "CUGGZ <xxxxx@xx.com> (https://juejin.cn/user/3544481220801815)"
}
另一种是对象形式:
{
"author": {
"name" : "CUGGZ",
"email" : "xxxxx@xx.com",
"url" : "https://juejin.cn/user/3544481220801815"
}
}
main
该字段用来指定加载的入口文件,在 browser 和 Node 环境中都可以使用。如果我们将项目发布为 npm 包,那么当使用 require
导入 npm 包时,返回的就是 main 字段所列出的文件的 module.exports
属性。如果不指定该字段,默认是项目根目录下的 index.js。如果没找到,就会报错。
该字段的值是一个字符串:
{
"main": "./src/index.js"
}
browser
如果您的模块打算在客户端使用,则应使用 browser 字段而不是 main 字段。这有助于提示用户它可能依赖于 Node.js 模块中不可用的原语。(例如:window
)
{
"browser": "./src/index.js"
}
repository
表示代码的存放仓库地址,通常有两种书写形式。第一种是字符串形式:
{
"repository": "https://github.com/facebook/react.git"
}
除此之外,还可以显式地设置版本控制系统,这时就是对象的形式:
{
"repository": {
"type": "git",
"url": "https://github.com/facebook/react.git"
}
}
homepage
项目主页的 URL。
例如:
"homepage": "https://github.com/my-project"
scripts
scripts 属性是一个对象,其中包含在包生命周期中的不同时间运行的脚本命令。key
是生命周期事件,value
是要在该点运行的命令。
{
"scripts": {
"serve": "webpack server --mode=development",
"dev": "webpack --mode=development",
"build": "webpack --mode=production"
}
}
可以配置任何 cmd 支持的命令,运行方式是 npm run 脚本名称
,不仅如此,npm 还对某些常用的脚本名称进行了简化,下面的脚本名称是不需要使用 run 的:
- start
- stop
- test
一些细节:
- 脚本中可以省略 npx,因为会将 node_module 中的 bin 目录添加到环境变量中,执行完成后会删除。
- start 脚本有默认值:node server.js。
dependencies
该字段中声明的是项目的生产环境中所必须的依赖包。当使用 npm 或 yarn 安装 npm 包时,该 npm 包会被自动插入到此配置项中:
npm install <PACKAGENAME>
yarn add <PACKAGENAME>
当在安装依赖时使用 --save 或 -S 参数,也会将新安装的 npm 包写入 dependencies 属性。
npm install --save <PACKAGENAME>
使用版本号
该字段的值是一个对象,该对象的各个成员,分别由模块名和对应的版本要求组成,表示依赖的模块及其版本范围:
{
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "~4.0.3",
}
}
使用 URL
您可以指定一个 URL 来代替版本范围。
使用 git URL
Git URL 的形式如下:
<protocol>://[<user>[:<password>]@]<hostname>[:<port>][:][/]<path>[#<commit-ish> | #semver:<semver>]
<protocol>
是 git
、git+ssh
、git+http
、git+https
或 git+file
之一。
如果提供了 #<commit-ish>
,它将用于克隆该提交。如果 commit-ish
的格式为 #semver:<semver>
,则 <semver>
可以是任何有效的 semver 范围或确切版本,并且 npm 将在远程存储库中查找与该范围匹配的任何标签或引用,就像注册表依赖项一样。如果未指定 #<commit-ish>
或 #semver:<semver>
,则使用默认分支。
git+ssh://git@github.com:npm/cli.git#v1.0.27
git+ssh://git@github.com:npm/cli#semver:^5.0
git+https://isaacs@github.com/npm/cli.git
git://github.com/npm/cli.git#v1.0.27
使用 GitHub URL
就像 git URL 一样,可以包含 commit-ish
后缀。例如:
{
"name": "foo",
"version": "0.0.0",
"dependencies": {
"express": "expressjs/express",
"mocha": "mochajs/mocha#4727d357ea",
"module": "user/repo#feature\/branch"
}
}
使用本地路径
从版本 2.0.0 开始,您可以提供包含包的本地目录的路径。可以使用 npm install -S
或 npm install --save
使用以下任何形式保存本地路径:
../foo/bar
~/foo/bar
./foo/bar
/foo/bar
在这种情况下,它们将被规范化为相对路径并添加到您的package.json
中。例如:
{
"name": "baz",
"dependencies": {
"bar": "file:../foo/bar"
}
}
此功能有助于本地离线开发和创建需要安装 npm 的测试,这些测试您不想访问外部服务器,但在将包发布到公共注册表时不应使用。
在这种情况下,运行
npm install
时,通过本地路径链接的包将不会安装自己的依赖项。您必须从本地路径本身内部运行npm install
。
devDependencies
devDependencies
中声明的是开发阶段需要的依赖包,如 Webpack、Eslint、Babel 等,用于辅助开发。它们不同于 dependencies
,因为它们只需安装在开发设备上,而无需在生产环境中运行代码。当打包上线时并不需要这些包,所以可以把这些依赖添加到 devDependencies
中,这些依赖依然会在本地指定 npm install
时被安装和管理,但是不会被安装到生产环境中。
当使用 npm 安装软件包时,指定以下参数后,新安装的 npm 包会被自动插入到此列表中:
npm install --save-dev <PACKAGENAME>
npm install -D <PACKAGENAME>
{
"devDependencies": {
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
}
}
private
private
字段可以防止我们意外地将私有库发布到 npm 服务器。只需要将该字段设置为 true
:
{
"private": true
}
语义化版本
版本规范:主版本号.次版本号.修订号
-
主版本号(major):当你做了不兼容的 API 修改。
-
次版本号(minor):当你做了向下兼容的功能性新增。
-
修订号(patch,补丁版本号):当你做了向下兼容的问题修正。
版本号范围
>
大于某个版本,例如:>1.2.1
,则说明依赖要大于 1.2.1 版本。
>=
大于等于某个版本,例如:>=1.2.1
,则说明依赖要大于等于 1.2.1 版本。
<
小于某个版本,例如:<1.2.1
,则说明依赖要小于 1.2.1 版本。
<=
小于等于某个版本,例如:<=1.2.1
,则说明依赖要小于等于 1.2.1 版本。
比较器可以用空格连接起来形成一个比较器集,它由它包含的所有比较器的交集来满足。例如,范围 >=1.2.7 <1.3.0
将匹配版本 1.2.7
、1.2.8
和 1.2.99
,但不包括版本 1.2.6
、1.3.0
或 1.1.0
。
一个范围由一个或多个比较器集组成,由 ||
连接。一个版本匹配一个范围,当且仅当至少一个 ||
分隔的比较器集中的每个比较器都满足该版本。范围 1.2.7 || >=1.2.9 <2.0.0
将与版本 1.2.7、1.2.9 和 1.4.6 匹配,但与版本 1.2.8 或 2.0.0 不匹配。
-
指定介于两个版本之间的范围,例如:1.2.3 - 2.3.4
,相当于 >=1.2.3 <=2.3.4
。
如果提供部分版本作为包含范围内的第一个版本,则缺失的部分将替换为零。例如:1.2 - 2.3.4
,相当于 >=1.2.0 <=2.3.4
。
如果提供部分版本作为包含范围内的第二个版本,则接受以提供的第二个版本开头的所有版本,但不接受大于提供的第二个版本。例如:1.2.3 - 2.3
,相当于 >=1.2.3 <2.4.0-0
;1.2.3 - 2
,相当于 >=1.2.3 <3.0.0-0
。
X/x/*
X
、x
或 *
中的任何一个都可以用来 “代替” [major, minor, patch]
中的某个数值。
-
*
相当于
>=0.0.0
,任何非预发行版本都满足。 -
1.x
相当于
>=1.0.0 <2.0.0-0
,匹配主版本号。 -
1.2.x
相当于
>=1.2.0 <1.3.0-0
,匹配主版本号和次要版本号。
部分版本范围被视为 X 范围,因此特殊字符实际上是可选的。
-
""
空字符串,相当于
*
或>=0.0.0
。 -
1
相当于
1.x.x
或>=1.0.0 <2.0.0-0
。 -
1.2
相当于
1.2.x
或>=1.2.0 <1.3.0-0
。
~
如果指定了次要版本,则允许补丁级更改。如果没有指定次要版本,则允许次要级别的更改。
-
~1.2.3
相当于
>=1.2.3 <1.(2+1).0
或>=1.2.3 <1.3.0-0
。 -
~1.2
相当于
>=1.2.0 <1.(2+1).0
或>=1.2.0 <1.3.0-0
(与1.2.x
相同)。 -
~1
相当于
>=1.0.0 <(1+1).0.0
或>=1.0.0 <2.0.0-0
(与1.x
相同)。 -
~0.2.3
相当于
>=0.2.3 <0.(2+1).0
或>=0.2.3 <0.3.0-0
。 -
~0.2
相当于
>=0.2.0 <0.(2+1).0
或>=0.2.0 <0.3.0-0
(与0.2.x
相同)。 -
~0
相当于
>=0.0.0 <(0+1).0.0
或>=0.0.0 <1.0.0-0
(同0.x
)。
^
允许对版本 1.0.0
及更高版本进行补丁更新和次要更新,对版本 0.X >=0.1.0
进行补丁更新,而对版本 0.0.X
没有更新。
-
^1.2.3
相当于
>=1.2.3 <2.0.0-0
。 -
^0.2.3
相当于
>=0.2.3 <0.3.0-0
。 -
^0.0.3
相当于
>=0.0.3 <0.0.4-0
。
解析插入符号范围时,缺少的 patch
值会降级为数字 0
,但即使在主要版本和次要版本均为 0
时,该值也会允许灵活性。
-
^1.2.x
相当于
>=1.2.0 <2.0.0-0
。 -
^0.0.x
相当于
>=0.0.0 <0.1.0-0
。 -
^0.0
相当于
>=0.0.0 <0.1.0-0
。
缺少的 minor
和 patch
值会将糖化为零,但也允许在这些值内灵活使用,即使主要版本为零也是如此。
-
^1.x
相当于
>=1.0.0 <2.0.0-0
。 -
^0.x
相当于
>=0.0.0 <1.0.0-0
。
^
允许对版本 1.0.0
及更高版本进行补丁更新和次要更新,对版本 0.X >=0.1.0
进行补丁更新,而对版本 0.0.X
没有更新。
-
^1.2.3
相当于
>=1.2.3 <2.0.0-0
。 -
^0.2.3
相当于
>=0.2.3 <0.3.0-0
。 -
^0.0.3
相当于
>=0.0.3 <0.0.4-0
。
解析插入符号范围时,缺少的 patch
值会降级为数字 0
,但即使在主要版本和次要版本均为 0
时,该值也会允许灵活性。
-
^1.2.x
相当于
>=1.2.0 <2.0.0-0
。 -
^0.0.x
相当于
>=0.0.0 <0.1.0-0
。 -
^0.0
相当于
>=0.0.0 <0.1.0-0
。
缺少的 minor
和 patch
值会将糖化为零,但也允许在这些值内灵活使用,即使主要版本为零也是如此。
-
^1.x
相当于
>=1.0.0 <2.0.0-0
。 -
^0.x
相当于
>=0.0.0 <1.0.0-0
。