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-project的package.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 react或npm 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>
npm link
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.json的files来指定只有哪些文件能被发布。
.gitignore中的文件不会被发布.npmignore中的文件不会被发布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.json、README、CHANGE / CHANGELOG / HISTORY 、LICENSE / LICENCE、NOTICE、main字段指向的文件。
main
用来规定模块的默认入口,默认值为 index.js。
const test = require('my-module') // 引入my-module模块的根目录的index.js文件
exports
功能类似main,同时存在exports和main时,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来执行命令,我们有其他的几种方式
./node_modules/.bin/akarapackage.json的script字段中填写执行方式,如{
"script": {
"run-my-script": "akara"
}
}npm exec akaranpx akara
script
npm除了 npm ci、npm install等内置脚本,还包括 hook script(pre/post script)和 lifecycle script。
pre & Post
对于一个脚本我们可能想要在其执行之前或之后执行某些操作,此时可以使用 pre或 post前缀。
{
"script": {
"test": "echo \"i'm test\"",
"pretest": "echo \"在test脚本执行前执行\"",
"posttest": "echo \"在test脚本执行后执行\""
}
}
LifeCycle
npm内置了一些生命周期脚本,如prepare、prepack等
{
"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字段指向 cjs或 umd模块,module字段指向 es模块,大多数情况下这两个字段都没什么用处。
在某些情况下,特别是使用 webpack打包模块时,当 webpack配置的 target为 web(默认值),会根据模块的 browser字段导入模块;通过设置 target为 node,会根据 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.json的 dependencies字段中我们经常能看见这种形式的版本号 "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.1、1.11.0等版本,但不能安装2.0.0或更高的版本。~:如果我在比较器中指定了次要版本,那么只允许补丁版本的更新;如果没有指定次要版本,那么可以允许次要版本的更新,所以通常情况~只允许补丁级别的更新。比如~1.10.0的依赖,意味着我们只能安装1.10.x的版本,不能安装1.11.0的版本。
而这也带来了一个新的问题,对于同一个项目在不同时机安装的依赖版本可能不一致,这就带来了相当大的风险和不可控性,特别是当依赖的某个包更新了一个漏洞,那也会影响到我们新构建的代码。
因此高版本yarn和npm都默认启用了lock机制,当我们npm install <name>安装依赖或npm update更新依赖的时候都会生成package-lock.json(yarn对应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的不同版本。
假设
node_modules存在B@1.1.0。然后我们安装的模块A依赖于B@^1.1.0,即使模块B最新版本已经到了1.9.0,我们也会复用原本已安装的模块。- A
- B@1.1.0假设
node_modules存在B@1.1.0。然后我们安装的模块A依赖于B@^1.2.0,则会安装最新的模块(如B@1.9.0)- A -> B@1.9.0
- B@1.1.0