跳到主要内容

NPM

.npmrc

NPM的配置文件

registry=https://registry.npmjs.org/
package-lock=false # 不启用NPM锁

命令

npm config

npm config set registry https://registry.npmjs.org/
npm config get prefix
npm config delete registry
npm config

npm init

初始化package.json

npm init -y

npm install

npm install <name> --save # yarn add <name>
npm install <name> --save-dev # yarn add <name> -D
npm install -g <name> # yarn global add <name>

npm install
npm ci # 通常用于CI,使用该命令时需要确保项目中存在 package-lock.json或 npm-shrinkwrap.json,并且当 package.json和 package-lock.json中依赖的版本不一致时 npm ci会抛出错误。

本地安装时,模块会被安装在/project/node_modules下,同时如果该模块的package.json中存在bin字段时,则会自动根据bin表示的字典在/project/node_modules/.bin下面创建对应的符号链接。

全局安装时,模块会被安装在/usr/local/lib/node_modules(以MacOS举例)下,同时如果该模块的package.json中存在bin字段时,则会自动根据bin表示的字典在/usr/local/bin下面创建对应的符号链接(可理解为Windows中的快捷方式)。

举个例子,akara-projectpackage.json中的bin字段如下,那么当全局安装akara-project时会创建/usr/local/bin/akara这个文件(符号链接),这个文件实际指向着akara-project根路径下的index.js。(其实这就是开发命令行工具的原理)

{
"bin": {
"akara": "index.js"
}
}

npm uninstall

npm uninstall <name> # yarn remove <name>

npm update

npm update <name> # yarn upgrade <name>

npm update

举个例子,如果我们的项目(采用锁机制)存在老版本的react@^16.0.0,此时我们(包括CI)只能拿到16.0.0版本的react。如果我们现在想要升级react可以采取两种方式:npm install reactnpm update react。前者更类似于重新安装react,并会重写package.json中的依赖关系;而npm update并不会改动package.json,它仅仅是根据package.json中的版本semver来重新安装可安装的最新react。二者都会重新生成package-lock.json

npm outdated

查看项目中哪些模块不是最新版本

npm outdated 

npm run

npm run start # 执行脚本
npm run-script <stage>

alias: npm ln

简单来说这个命令存在两种用法,可用于将本地模块链接到全局,或是将全局模块链接到本地。

用法一:

当我们位于模块akara-project项目中时,直接执行npm link会把当前模块链接到全局(具体来说的话,会在/usr/local/lib/node_modules下创建一个符号链接指向着akara-project这个模块,如果该模块的package.json存在bin字段时还会在/usr/local/bin下面创建对应的符号链接)

用法二:

当我们位于某个项目中时,并假设我们已经全局安装了一个模块(如pm2),此时在项目中执行npm link webpack会把全局模块链接到本地(具体来说的话,会在当前项目的node_modules创建一个符号链接pm2指向着全局的pm2模块,如果该模块的package.json存在bin字段时还会在/project/node_modules/.bin下面创建对应的符号链接)

通过结合方法一和方法二我们可以实现这样的功能:假设我们同时维护着项目A和模块B,并在项目A中引用着模块B。那么比较传统的方法来维护这两个库是这样的,更新完模块B后发布,然后在项目A中更新模块B从而查看最新的效果;而通过npm link我们可以简化这个流程,我们只需要先在B模块中通过npm link来把B模块链接到全局,然后在A项目中通过npm link B来把全局B模块链接到A项目本地。

npm exec

可以直接执行node_modules/.bin下的可执行文件

npm exec webpack # 等于npx webpack

npm publish

npm publish # 发布模块
npm unpublish --force # 下架模块

npm publish --access publish # 发布公共模块

在通过npm login登陆了npm账户后,我们可以在任意项目下通过npm publish来进行模块的发布,此时NPM镜像源需要是官方镜像源。

除了通常的模块,我们还可以发布类似@akara/my-package这样的作用域模块,此时我们需要npm publish --access publish。这是因为NPM模块分为公共模块和私有模块,通常我们发布的都是公共模块,而发布私有模块是收费的,同时发布作用域模块默认是发布私有模块,因此我们需要显式的制定模块的类型。

发布NPM模块时,.gitignore.npmignore中指定的文件不会被发布出去。除此之外我们还可以通过package.jsonfiles来指定只有哪些文件能被发布。

  1. .gitignore中的文件不会被发布
  2. .npmignore中的文件不会被发布
  3. package.json中的 files字段指定哪些文件会被发布

npm view

查看一个模块的信息

npm view <name> # e.g npm view antd

npm version

npm version # 查看当前版本
npm version patch # 升级一个补丁版本,同时自动git commit并打上版本号对应的git tag,如v1.0.1
npm version minor # 升级一个小版本,如v1.1.1
npm version major # 升级一个大版本,如v2.0.0

npm audit

npm audit # 查看当前项目所有依赖模块的漏洞
npm audit --fix # 更新所有存在漏洞的模块来修复漏洞

当我们安装模块时会自动提示当前版本的模块的漏洞,我们也可以通过该命令来查找当前项目所有依赖中可能存在的漏洞

npm fund

npm fund

当我们安装模块时会自动提示有多少个模块正在寻找投资/资助,我们也可以通过该命令来查看具体是哪些命令在寻找投资

package.json

type

.cjs结尾的文件会被视为CommonJS模块,以.mjs结尾的文件会被视为ES模块,而普通的.js文件则会根据type字段的不同视为不同的模块,默认不带type视为CommonJS模块,type: 'module'则视为ES模块。

files

对于一些模块而言,用户只需要引用该模块源码构建后的产物,而并不想连源代码也一起安装。这样的模块通常会通过files字段来规定哪些模块才会被发布,如"file": ["dist", "README.md"]

需要注意的是以下文件永远都会被外部下载:package.jsonREADMECHANGE / CHANGELOG / HISTORY LICENSE / LICENCENOTICEmain字段指向的文件。

main

用来规定模块的默认入口,默认值为 index.js

const test = require('my-module') // 引入my-module模块的根目录的index.js文件

exports

功能类似main,同时存在exportsmain时,exports字段的优先级更高

{
"exports": {
".": "./index.js",
"./test": "./src/test.js"
}
}
const test = require('my-module') // ./index.js
const test2 = require('my-module/test') // ./src/test.js

可以看到一旦使用了exports字段,那么模块的引用规则就和以往有较大的区别,此时我们不再能根据模块的任意路径来引用相对应的文件,只能根据exports所指定的映射关系来引用所给定的文件。

exports还能够根据导入模块时使用的是 require还是 import选择不同的导出。

{
"exports": {
".": {
"require": "./a.js",
"import": "./b.mjs"
}
}
}

bin

如之前说过那样,当我们通过npm install安装模块时,会根据该模块package.json中的bin字段来创建指向指定文件的符号链接

{
"bin": {
"akara": "index.js"
}
}

假设akara-project的配置如上,那么全局安装akara-project时会创建/usr/local/bin/akara符号链接(指向着/usr/local/lib/node_modules/akara-project/index.js),所以现在我们可以直接在命令行中输入akara来执行对应的index.js(当然实际上我们还需要两个小步骤,一是我们需要规定index.js这个文件执行的环境,因此需要在index.js代码的第一行加上#!/usr/bin/env node;二是我们需要通过执行chmod +x index.js来给予该文件可执行权限)

如果我们是本地安装而不是全局安装akara-project,那么创建的符号链接akara会被放在project/node_modules/.bin下面,此时无法直接通过在命令行输入akara来执行命令,我们有其他的几种方式

  1. ./node_modules/.bin/akara

  2. package.jsonscript字段中填写执行方式,如

    {
    "script": {
    "run-my-script": "akara"
    }
    }
  3. npm exec akara

  4. npx akara

script

npm除了 npm cinpm install等内置脚本,还包括 hook scriptpre/post script)lifecycle script

pre & Post

对于一个脚本我们可能想要在其执行之前或之后执行某些操作,此时可以使用 prepost前缀。

{
"script": {
"test": "echo \"i'm test\"",
"pretest": "echo \"在test脚本执行前执行\"",
"posttest": "echo \"在test脚本执行后执行\""
}
}

LifeCycle

npm内置了一些生命周期脚本,如prepareprepack

{
"script": {
"prepare": "echo \"hello akara\""
}
}

dependencies

项目的依赖

devDependencies

项目的开发依赖。如果我们在NPM发布了一个模块A,之后当我们 npm install A时会自动安装模块A的依赖,但不会安装模块A的开发依赖。

peerDependencies

假设存在A库,该库存在一个插件B,那么很明显插件B自身是不依赖于库A的,但是又需要你的项目中存在库A,那么插件B的 package.json中就可以通过 peerDependencies来指定同级依赖关系。比如插件B只能在1.0版本的A库中起作用,那么 peerDependencies可能是 A@1.0.0,当你的项目中同时安装了插件B和2.0版本的A库时就会出现警告。

resolutions

假设我们项目直接依赖react-router,而react-router内部又依赖@types/react: '*'。这意味着当我们安装react-router时会自动安装最新版本的@types/react,又已知最新版本的@types/react(比如v18)引入了一些破坏性变更,我们此时希望能够安装更低版本的@types/react来避免错误提示,这个时候可以给package.json添加resolutions字段,并通过yarn install重新安装。Selective dependency resolutions

{
resolutions: {
'@types/react': '^17.0.0'
}
}

browser | module

一般 browser字段指向 cjsumd模块,module字段指向 es模块,大多数情况下这两个字段都没什么用处。

在某些情况下,特别是使用 webpack打包模块时,当 webpack配置的 targetweb(默认值),会根据模块的 browser字段导入模块;通过设置 targetnode,会根据 module字段导入模块。

另外,当 package.json不存在对应的入口字段,会根据 browser -> module -> main的优先级导入模块。这个优先级是根据配置 resolve.mainFields字段指定的,我们可以通过修改该字段来调整优先级:

// webpack.config.js
module.exports = {
target: 'node', // 默认值web
resolve: {
mainFields: ['main', 'module', 'browser'] // 默认值 ['browser', 'module', 'main']
}
}

package-lock.json

package.jsondependencies字段中我们经常能看见这种形式的版本号 "react": "^17.0.2""xx": "~0.10.0",这种写法通常被称为semver表示法,三个数字分别表示主要版本、次要版本、补丁版本。

当我们使用 npm install <name>来安装依赖,或者是在已有的项目中使用 npm update来更新依赖,都会根据 semver规则安装对应版本的模块,也就是说实际安装版本并不是固定的

  • ^:表示只会执行不更改最左边非零数字的更新。比如 ^0.10.0,意味着我们可以安装(或更新,下同)0.10.1等版本,但不能安装 0.11.0或更高的版本;又比如 ^1.10.0,意味着我们可以安装 1.10.11.11.0等版本,但不能安装 2.0.0或更高的版本。
  • ~:如果我在比较器中指定了次要版本,那么只允许补丁版本的更新;如果没有指定次要版本,那么可以允许次要版本的更新,所以通常情况 ~只允许补丁级别的更新。比如 ~1.10.0的依赖,意味着我们只能安装 1.10.x的版本,不能安装 1.11.0的版本。

而这也带来了一个新的问题,对于同一个项目在不同时机安装的依赖版本可能不一致,这就带来了相当大的风险和不可控性,特别是当依赖的某个包更新了一个漏洞,那也会影响到我们新构建的代码。

因此高版本yarnnpm都默认启用了lock机制,当我们npm install <name>安装依赖或npm update更新依赖的时候都会生成package-lock.jsonyarn对应yarn.lock),那当其他人clone项目并npm install时则会根据package-lock.json来安装指定版本的模块。


不过很明显锁不锁版本都有对应的好处和坏处,所以社区对是否锁版本还存在着一些争论

另外,无论是否使用 lock机制,都不应该把 package-lock.json写入 .gitignore中。

如果我们使用 lock机制,应该直接把 package-lock.json提交进仓库;如果我们不使用 lock机制,则应该在 .npmrc中写入 package-lock=false来关闭 lock机制,并把 package-lock.json提交到仓库中。参考

node_modules

NPM的早期版本,node_modules使用嵌套结构来管理模块之间的依赖关系。而我们的很多模块都又可能依赖于同一个模块,这样的结构可能导致性能的浪费。

- A -> B
- C -> B

所以在 NPM@3.x以后,node_modules主要使用扁平结构来管理模块之间的依赖关系。假设我们安装了A和C模块,这两个模块同时依赖于同一个版本的B模块,此时 node_modules结构如下。

- A
- C
- B

不过有的时候,我们安装的模块A和C可能依赖于同一个模块B的不同版本。

  1. 假设 node_modules存在 B@1.1.0。然后我们安装的模块A依赖于 B@^1.1.0,即使模块B最新版本已经到了 1.9.0,我们也会复用原本已安装的模块。

    - A
    - B@1.1.0
  2. 假设 node_modules存在 B@1.1.0。然后我们安装的模块A依赖于 B@^1.2.0,则会安装最新的模块(如 B@1.9.0

    - A -> B@1.9.0 
    - B@1.1.0