🗒️配置 npm
1. 下载和安装
要从 public npm registry 安装和发布 packages,就必须安装 Node.js 和 npm 命令行工具。强烈建议使用 Node version manager 来安装 Node.js and npm,而不建议用 Node installer(因为它会把 npm 安装在一个具有 local 权限的目录里,这会导致 permissions errors 当我们全局运行 npm 包时)。
Node version manager:
Windows
nodist
nvm-windows
node -v
npm -v
2. 目录结构
介绍 npm 使用的 folder structures。
npm 会在我们的电脑上放各种东西。当然,这正是它的工作。
2.1 不同模式
local 安装(默认):会安装在
./node_modules
global 安装(
-g
):会安装在prefix
配置的文件夹下prefix
配置默认是 node 安装的位置大多数系统,
/usr/local
Windows,
%AppData%\npm
Unix,它是上一级(one level up),因为 node 通常安装在
{prefix}/bin/node
而不是{prefix}/node.exe
若
prefix
没设置,则就用当前 package 或者当前工作目录的根目录
若想 require()
使用,就 local 安装。若想在 command line 里使用,就 global 安装。若想同时使用,就在两个地方都安装,或者使用 npm link
。
2.2 安装位置
packages 或
node modules
./node_modules
Unix 系统
{prefix}/lib/node_modules/
Windows 系统
{prefix}/node_modules/
可执行文件 be linked to
./node_modules/.bin/
Unix 系统
{prefix}/bin/
Windows 系统
{prefix}/
操作手册 be linked to
不安装
{prefix}/share/man
cache 文件
Posix
~/.npm
Windows
%AppData%/npm-cache
临时文件
默认是环境变量 TMPDIR
, TMP
, TEMP
Unix
/tmp
Windows
c:\windows\temp
补充说明:
packages
scoped packages 的安装位置相同,只是它们又被分进了子目录
若是 local 安装的
可以使用
require("packagename")
去 load 它的 main module使用
require("packagename/lib/path/to/sub/module")
去 load 其它 modules
可执行文件
对于 local 安装的,就能通过 npm run script 了
cache 文件
其路径可以由
cache
config 参数控制的详见
npm cache
临时文件
其路径可以由
tmp
config 参数配置控制每次 run program 时,临时文件都会在此根目录下指定一个唯一的文件夹,并在成功退出时被删除
2.3 prefix
补充
prefix
补充当 local 安装时,npm 首先尝试找到合适的 prefix
folder。这样的话,即便我们碰巧 cd
进入了其它目录,npm install xxx
也会安装到合理的根目录中。
从
$PWD
开始,npm 将遍历 folder tree 以检查包含package.json
文件或node_modules
文件夹的 folder。如果找到了,那它就被视为有效的 current directory,以运行 npm commands
这个逻辑受 git 的
.git-folder
的查找逻辑的启发。
如果没有找到 package root,则使用 current folder。
当运行
npm install foo@1.2.3
时package 被 loaded 到 cache 中,然后 unpacked 到
./node_modules/foo
。接着,所有 foo 的依赖都被类似地 unpacked 到
./node_modules/foo/node_modules/..
所有 bin 文件都 symlinked(符号链接)到
./node_modules/.bin/
以便在运行 npm scripts 时可以找到它们。
对于 global 安装,packages 的安装方式大致相同,只是使用的文件夹不同。
2.4 循环+冲突+文件夹优化
cycles, conflicts 和 folder parsimony(简约)
cycles 是使用 node 的 module system 的属性来处理的,它会在目录中查找 node_modules
文件夹。所以,在每个阶段,如果某个 package 已经安装在了其祖先的 node_modules
文件夹中,那么它就不在当前位置安装了。
比如 foo -> bar -> baz
,如果再加一个 baz -> bar
,就不用将 bar
的另一个副本再放到 .../baz/node_modules/
里了,因为当 baz 调用 require("bar")
时会从 foo/node_modules/bar
里获得安装的副本。
这种 shortcut(快捷方式)只使用在多个嵌套的 node_modules
文件夹中需要安装 exact same version 的 package 时。当 a 的版本不一样时还是有可能出现 a/node_modules/b/node_modules/a
的。然而,如果不多次重复 exact same package,将始终 prevent(阻止/避免)无限 regress(倒退)。
还可以做个优化,就是在尽可能高的级别下安装 dependencies,在 localized "target" folder (hoisting, 提升)。从版本 3 开始,npm 默认 hoist(提升)dependencies。
比如如下 dependency graph:
foo
+-- blerg@1.2.5
+-- bar@1.2.3
| +-- blerg@1.x (latest=1.3.7)
| +-- baz@2.x
| | +-- quux@3.x
| | +-- bar@1.2.3 (cycle)
| +-- asdf@*
+-- baz@1.2.3
+-- quux@3.x
+-- bar@1.2.3
第一:先处理第一层的依赖,即 foo 的依赖项
如下:
`foo`
+-- node_modules
+-- `blerg@1.2.5`
+-- `bar@1.2.3`
+-- `baz@1.2.3`
第二:再依次处理第二层的依赖
对于
blerg@1.2.5
,它没有依赖项。可以考虑 hoist 了,但因为其直接父已经是 root 了所以忽略对于
bar@1.2.3
,它有三个依赖项blerg@1.x
不用安了,因为其祖先已经有了,且版本匹配baz@2.x
需要安,因为它和祖先的baz@1.2.3
版本不匹配asdf@*
需要安
对于
baz@1.2.3
,它有一个依赖项quux@3.x
需要安
结果如下:
foo
+-- node_modules
+-- `blerg@1.2.5`
+-- `bar@1.2.3`
| +-- node_modules
| | +-- `baz@2.x`
| | +-- `asdf@*`
+-- `baz@1.2.3`
| +-- node_modules
| | +-- `quux@3.x`
第三:再依次处理第三次依赖
bar@1.2.3 -> baz@2.x
,它有一个依赖quux@3.x
不用安,因为祖先里已经有了此时
bar@1.2.3 -> baz@2.x
是个叶子节点,但因为 root 下已经有另外一个版本的了所以不提升
bar@1.2.3 -> asdf@*
,它没有依赖。可以考虑 hoist,将其提升到 rootbaz@1.2.3 -> quux@3.x
,它有一个依赖bar@1.2.3
不用安,因为祖先里已经有了此时
baz@1.2.3 -> quux@3.x
是个叶子节点,所以提升到 root
结果如下:
foo
+-- node_modules
+-- blerg@1.2.5
+-- bar@1.2.3
| +-- node_modules
| | +-- `baz@2.x`
+-- `asdf@*`
+-- baz@1.2.3
+-- `quux@3.x`
所以,最终的 folder structure 如上,我们把所有 dependencies 都 hoist 到尽可能高的级别。当然,真实项目里的版本都是符合条件的明确版本。
我们可以使用 npm ls
命令查看安装的 graphical breakdown(细分)。
2.5 发布
一旦发布,npm 将会在 node_modules
文件夹中查找。只要是不在 bundleDependencies
数组中的 items,就不会被包含在 package tarball(压缩包)中。
这允许 package 维护者在本地安装他们所有的 dependencies(和 dev dependencies),但只 re-publish 那些在别处找不到的 items。相关详情可查阅 package.json。
3. .npmrc
.npmrc
npm 配置文件。npm 从 command line, environment variables 和 npmrc
files 获取它的 config settings。
四个相关文件是:
每个项目的配置文件
/path/to/my/project/.npmrc
每个用户的配置文件
~/.npmrc
全局配置文件
$PREFIX/etc/npmrc
npm 内置配置文件
/path/to/npm/npmrc
npm config
命令可用来更新和编辑 user 和 global npmrc files 的内容。
所有的 npm 配置文件都是 ini 格式的 key = value
参数对列表。注释是以 ;
或 #
开头的行。
4. package.json
package.json
package.json
必须是实际的 JSON,而不仅仅是一个 JavaScript 对象字面量。
{
"name": "hello-world", // *
"version": "1.0.0", // *
// `npm search`
"description": "", // 会在 npm 网站上显示
"keywords": "",
// 二选一,提示用户是依赖于 Node.js modules 还是 primitives
"main": "index.js", // 程序的 primary entry point
"browser": "", // 如果该 module 打算是在 client-side 使用,那么更应该用此字段
// 脚本,是一个包含 script commands 的 dictionary
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
// default some values based on package contents.
"start": "node server.js",
"install": "node-gyp rebuild",
},
"config": {}, // 设置 configuration parameters,用在升级过程中持续存在的 package scripts
// 依赖
"dependencies": {},
"devDependencies": {},
"peerDependencies": {},
"peerDependenciesMeta": {},
"bundleDependencies": {},
"optionalDependencies": {},
"overrides": {}, // 对依赖的依赖做特定修改
// people 相关
"author": "one person",
"contributors": "an array of people", // ~ `AUTHORS` file
"person": {
"name": "",
"email": "",
"url": ""
},
// 项目相关
"homepage": "", // project homepage
"bugs": "https://.../issues", // 也可以是个对象
"repository": { // 代码放置的地方
"type": "git",
"url": "https://github.com/npm/cli.git"
},
// 要求相关
"engines": {}, // 指定 node 和 npm 版本
"os": {}, // 指定 operating systems
"cup": {},
// 更多
"license": "ISC",
"private": true, // npm will refuse to publish it
"publishConfig": {}, // 在 publish-time 使用的 config values
"workspaces": [], // file patterns 数组
"files": "", // file patterns 数组,描述当该 package 作为依赖项安装时要包含的 entries 的
"bin": "", // executable files, 可执行文件
"man": "", // `man` 命令要查找的文件
"directories": {}, // CommonJS Packages spec
}
更多信息详见 npm CLI / Configuring npm / package.json。
这里介绍的许多行为都受 npm CLI / Using npm / Config 配置设置的影响。
5. lockfile
5.1 package-lock.json
package-lock.json
当 npm 修改 node_modules
树或修改 package.json
时,就会自动生成 package-lock.json
。package-lock.json
描述了生成的 exact tree,以便后续的 install 能生成相同的树,而不管中间 dependency 的更新。
{
"name": "", // 同 package.json
"version": "", // 同 package.json
"lockfileVersion": "", // integer, 从 1 开始
"packages": {},
"dependencies": {}
}
5.1.1 应提交 repo
package-lock.json
应该提交到 source repository,目的有:
确保后续的 install 能生成完全相同的依赖关系
不用提交
node_modules
目录本身提高 tree changes 的可见性,通过可读的 source control diffs
优化安装过程,因为 npm 可以跳过重复的 metadata resolution
从 npm v7 开始,lockfiles 包含了足够的信息以获取 package tree 的完整 picture,减少读取
package.json
文件的需要,允许显著的性能提升
5.1.2 隐藏的 lockfiles
为了避免重复处理 node_modules
文件夹,从 npm v7 开始使用一个 hidden lockfile node_modules/.package-lock.json
。它包含 tree 的相关信息,用于代替读取整个 node_modules
hierarchy(层次结构),前提是满足以下条件:
它引用的所有 package folders 都存在于
node_modules
层次结构中不存在 package folders 是在
node_modules
层次结构中却没在 lockfile 中列出file 的修改时间至少和其引用的所有 package folders 一样新
npm 的旧版本会忽略该 hidden lockfile。
5.2 npm-shrinkwrap.json
npm-shrinkwrap.json
可发布的 lockfile(锁文件)。
该文件是由 npm shrinkwrap
命令创建的。它等价于 package-lock.json
,不同的是 npm-shrinkwrap.json
可能会在发布 package 时包含。
5.3 对比
package-lock.json
vs npm-shrinkwrap.json
相同点:格式相同,在项目的根目录下发挥着相似的功能
不同点:
package-lock.json
不能发布,并且在项目的根目录以外的任何地方该文件都会被忽略npm-shrinkwrap.json
允许发布,并且从 point 定义 dependency tree。除非部署 CLI 工具或以其它方式使用发布过程来生成 production packages,否则不建议这样做
如果 package 根目录中同时有这两个文件,那么 npm-shrinkwrap.json
会优先于 package-lock.json
文件。
Last updated