🗒️配置 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 -v2. 目录结构
介绍 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 文件 - 其路径可以由 - cacheconfig 参数控制的
- 详见 - npm cache
 
- 临时文件 - 其路径可以由 - tmpconfig 参数配置控制
- 每次 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,将其提升到 root
- baz@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
.npmrcnpm 配置文件。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.jsonpackage.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