1. npm简介
先来看下官方介绍:
npm makes it easy for JavaScript developers to share and reuse code, and it makes it easy to update the code that you’re sharing
大概的意思就是: npm 是一个包管理器,让 JavaScript 开发者分享、复用、更新代码更方便。
npm有两层含义:一层含义是Node的开放式模块登记和管理系统(存包系统),网址:https://www.npmjs.com/;另一层含义是Node默认的模块管理器,是一个命令行软件,可以用来安装、管理和发布Node模块。
我们考虑下,没有npm之前前端程序员的操作方式:
- 当项目需要jQuery,需要手动去 jQuery 官网下载 jQuery
- 当项目需要BootStrap ,手动去 BootStrap 官网下载 BootStrap
- 当项目需要Underscore ,手动去 Underscore 官网下载 Underscore
.......
当有了npm之后,这些事情都不需要我们手动去处理了,npm自动去帮我们处理,其主要思想就是使用npm来把这些代码集中到一起来管理。
- 买个服务器作为代码仓库(repository,也就是:https://www.npmjs.com/),在里面放所有需要被共享的代码;
- 发邮件通知 jQuery 、Bootstrap 、Underscore 的作者使用 npm publish 把代码提交到 repository 上,分别取名 jquery、bootstrap 和 underscore;
- 社区里的其他人如果想使用这些代码,就把 jquery、bootstrap 和 underscore 写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码;
- 下载完的代码出现在 node_modules 目录里,就可以随意使用了。
2. npm安装
2.1. Node.js和npm
Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台,简单的说 Node.js 就是运行在服务端的 JavaScript。
npm 的发展是跟 Node.js 的发展相辅相成的。Node.js是由一个在德国工作的美国程序员 Ryan Dahl 写的。因为 Node.js 缺少一个包管理器,于是他和npm的作者一拍即合、抱团取暖,最终 Node.js 内置了 npm,这也就是 npm( Node Package Manager) 名字的由来。
2.2. npm安装
npm 是依附于 Node.js 的,所以只要安装了Node.js就安装了npm,我们可以去它的官网 https://nodejs.org/en/download/ 下载安装 node.js后就自动安装了npm。
Node.js安装请参考:https://www.runoob.com/nodejs/nodejs-install-setup.html
但是,Node附带的npm可能不是最新版本,使用如下命令,更新到最新版本
npm install npm@latest -g #npm@latest 就是 <packageName>@<version> 的格式 #在下载其他模块时也是这个格式 # -g 代表全局安装
3.package.json文件
package.json文件是管理本地安装 npm 包的最好方式,其主要有如下几点作用:
- 作为一个描述文件,描述了你的项目依赖哪些包
- 允许我们使用 “语义化版本规则”指明你项目依赖包的版本
- 让你的构建更好地与其他开发者分享,便于重复使用
3.1. package.json的创建
package.json文件的创建主要是使用如下命令进行创建。
npm init 或 npm init --yes
如上操作,输入 npm init 后会弹出一堆问题,我们可以输入对应内容,也可以使用默认值。在回答一堆问题后输入 yes 就会生成图中所示内容的 package.json 文件
当然我们可以直接输入 npm init --yes 跳过回答问题步骤,直接生成默认值的 package.json 文件。
3.2. package.json内容介绍
package.json文件中至少包含的两个元素:
- “name”:全部小写,没有空格,可以使用下划线或者横线
- “version”:x.x.x 的格式,符合“语义化版本规则”
其他元素:
- “description”:描述信息,有助于搜索,如果 package.json 中没有 description 信息,npm 使用项目中的 README.md 的第一行作为描述信息。这个描述信息有助于别人搜索你的项目,因此建议写好 description 信息
- “main”: 入口文件,一般都是 index.js
- “scripts”:支持的脚本,默认是一个空的 test
- “keyword”s:关键字,有助于在人们使用 npm search 搜索时发现你的项目
- “author”:作者信息
- “license”:默认是 MIT
- “bugs”:当前项目的一些错误信息,如果有的话
- “dependenies”:当前项目的依赖
当然我们还可以使用命令来设置环境变量,即为npm init设置默认值,当我们执行npm init的时候,就会写入我们设置的默认值。
npm set init.author.email "wendy@qq.com" npm set init.author.name "wendy" npm set init.license "MIT"
3.3. 指定依赖的包
项目的依赖包就需要在package.json文件中进行指定,这样其他人如果需要,就可以使用npm install命令进行下载。
包的依赖用两种方式:
- dependencies:在生产环境中需要用到的依赖
- devDependencies:在开发、测试环境中用到的依赖
如下就简要的指定了生成环境中,开发、测试环境中需要用到的依赖:
{ "name": "npmstudy", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "'wendy'", "license": "ISC", "devDependencies": { "babel-core": "^6.14.0", "babel-loader": "^6.2.5", "babel-preset-es2015": "^6.18.0" }, "dependencies": { "weex-html5": "^0.3.2", "weex-components": "*" } }
3.4. 语义化版本规则(针对的是package.json内的依赖)
从上面的依赖书写方式我们可以看到,每一个依赖都是使用键值对(key,value)的方式进行书写的,key表示依赖包的名称,而value则表示包的版本,而包的版本正是用到了“语义化版本规则”。
在了解语义化版本规则前,我们先来了解下npm 包提供者应该注意的版本号规范和标准:
- 补丁版本:解决了 Bug 或者一些较小的更改,增加最后一位数字,比如 1.0.1
- 小版本:增加了新特性,同时不会影响之前的版本,增加中间一位数字,比如 1.1.0
- 大版本:大改版,无法兼容之前的,增加第一位数字,比如 2.0.0
在了解了如上的版本号和标准后,我们就可以针对自己的需要填写依赖包的版本规则,其大体有如下三种(假设当前版本为1.0.2):
- 如果只打算接受补丁版本的更新(也就是最后一位的改变),就可以这么写:
- 1.0
- 1.0.x
- ~1.0.2
- 如果接受小版本的更新(第二位的改变),就可以这么写:
- 1
- 1.x
- ^1.0.2
- 如果可以接受大版本的更新(自然接受小版本和补丁版本的改变),就可以这么写:
- *
- x
4. npm常用命令
4.1. 基本命令
npm help:查看npm命令列表 npm -l:查看npm各命令的简单用法 npm -v:查看npm版本 npm config list -l:查看npm配置 npm init:初始化一个新的package.json npm set:设置环境变量
4.2. npm config
此命令主要用户查看npm配置相关内容,其主要有如下几个常用命令:
npm config list -l:查看npm配置 npm config set prefix directoryPath:设置项目路径directoryPath为全局安装目录 npm config set save-prefix ~ :让npm install --save和npm install --save-dev安装新模块时,允许的版本范围从克拉符号(^)改成波浪号(~),即从允许小版本升级,变成只允许补丁包的升级 npm config set init.author.name $name:指定使用npm init时,生成的package.json文件的name的默认值 npm config set init.author.email $email:指定使用npm init时,生成的package.json文件的email的默认值
4.3. npm info
此命令主要用于查看每个模块的具体信息,比如查看jQuery的具体信息:
npm info jquery
4.4. npm search
此命令用于搜索npm仓库中指定包的版本,它后面可以跟字符串,也可以跟正则表达式,例如查看react的版本
npm search react
4.5. npm list
此命令主要用于以树型结构列出当前项目安装的所有模块,以及它们依赖的模块。
npm list:列出当前项目安装的所有模块及依赖的模块 npm list -global :列出全局安装的模块及依赖的模块 npm list packageName:列出单个模块
4.6. npm install
4.6.1. 基本用法
Node模块就是采用npm install命令来进行安装的。
每个模块可以“全局安装”,也可以“本地安装”。“全局安装”指的是将一个模块安装到系统目录中,各个项目都可以调用。一般来说,全局安装只适用于工具模块,比如eslint和gulp。“本地安装”指的是将一个模块下载到当前项目的node_modules子目录,然后只有在项目目录之中,才能调用这个模块。
命令格式:
#本地安装 npm install <package name1> <package name2> #全局安装 npm install -global <package name> npm install -g <package name> #从Github代码库地址安装 npm install git://github.com/package/path.git:未指定版本,则拉取最新的版本 npm install git://github.com/package/path.git#0.1.0:拉取指定的版本
安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也不会更新,除非我们使用npm install命令时指定了需要强制重装,但不是强制更新,也就是相当于删除包之后再重新装一遍。
npm install <packageName> --force
或者
npm install <packageName> --f
如果我们需要所有的包都重装或者说全部更新
rm -rf node_modules:Linux下删除,如果Windows下rd /s /d node_modules
npm install :按照package.json文件中的依赖进行全部安装
#需要注意的是PS:
1、npm install <package name>实际上是 npm install <package name>@latest,所以未指定包名时表示安装最新的版本。
2、如果npm install 的话,就会按package.json中指定的版本进行安装,即使有最新的也不会更新(所以这里会涉及到缓存的问题),
如果有shrinkwrap.json文件的话,npm install是按照这个文件里面锁定的版本来进行更新的。
3、npm install <package name>时是默认--save的,即默认会把安装包的信息写在dependencies中的
4、npm install 可以简化为npm i
4.6.2. 安装不同的版本
npm install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号
npm install <packageName>@version 如: npm install sax@latest:安装最新版本 npm install sax@0.1.1:安装0.1.1版本 npm install sax@">=0.1.0 <0.2.0":安装大于0.1.0并小于0.2.0的版本
如果使用--save-exact参数,会在package.json文件指定安装模块的确切版本。(-exact是配置中的一个参数,exact=true,就会按package.json中指定的确切版本就行安装)
npm install readable-stream --save(或-S) --save-exac npm install命令可以使用不同参数,指定所安装的模块属于哪一种性质的依赖关系,即出现在packages.json文件中依赖的哪一项中: --save:模块名将被添加到dependencies,可以简化为参数-S。 --save-dev: 模块名将被添加到devDependencies,可以简化为参数-D
$ npm install sax --save :这是默认情况下 $ npm install node-tap --save-dev
# 或者 $ npm install sax -S $ npm install node-tap -D #npm install默认会安装dependencies字段和devDependencies字段中指定的所有模块 #如果使用--production参数,可以只安装dependencies字段的模块。 $ npm install --production
# 或者 $ NODE_ENV=production npm install
4.7. npm update
此命令主要用户更新指定包的版本,它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。
# 升级本地所有模块 npm update # 升级本地安装的具体模块 npm update [package name] # 升级全局安装的模块 npm update -global [package name]
使用-S,-D参数会在安装的时候更新package.json里面模块的版本号
- --save:模块名将被添加到dependencies,可以简化为参数-S。
- --save-dev: 模块名将被添加到devDependencies,可以简化为参数-D
# 升级本地安装的模块 npm update [package name] -S # 升级全局安装的模块 npm update -global [package name] -D
需要注意两点:
1、从npm v2.6.1 开始,npm update只更新顶层模块,而不更新依赖的依赖,以前版本是递归更新的;
2、npm update更新是遵从上面说的配置文件中的--save-prefix的设置的,如果是~,则表示只允许补丁包的升级,^表示允许小版本升级,*则表示允许所有的升级。
4.8. npm uninstall
此命令用于卸载已安装的模块。
# 卸载本地安装的模块 npm uninstall [package name] # 卸载全局模块 npm uninstall [package name] -global
4.9. npm run
npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用。
#假设package.json文件的内容 { "name": "myproject", "devDependencies": { "jshint": "latest", "browserify": "latest", "mocha": "latest" }, "scripts": { "lint": "eslint .”, "test": "mocha test/" } }
从上面我们可以看出,scripts字段中指定了两项命令lint和test。
- 当我们在命令行输入npm run-script lint或者npm run lint,就会执行eslint .的命令;
- 当输入npm run-script test或者npm run test,就会执行mocha test/。
4.9.1. npm run使用
- npm run是npm run-script的缩写,一般都使用前者,但是后者可以更好地反应这个命令的本质;
- npm run命令会自动在环境变量添加node_modules/.bin目录,所以scripts字段里面调用命令时不用再加上路径,比如上面的脚本中,我们npm run lint实际上执行“./node_modules/.bin/eslint .”;
- 所以结合第二点,mocha test/ 命令中,mocha的路径实际为:当前项目路径/node_modules/.bin/mocha,而test/则表示:当前项目路径/test/;
- npm run如果不加任何参数,直接运行,会列出package.json里面所有可以执行的脚本命令;
- npm内置了两个命令简写,npm test等同于执行npm run test,npm start等同于执行npm run start,其他的均为npm run scriptName;
- npm run会创建一个Shell,执行指定的命令,并临时将node_modules/.bin加入PATH变量,这意味着本地模块可以直接运行
4.9.2. npm run参数
npm run 命令运行时,可以给命令添加参数
#假如script脚本 "scripts": { "test": "mocha test/" }
当我们使用npm test时,实际上就是运行了命令mocha test/,即使用mocha运行所有test子目录的测试脚本。
如果我们需要添加参数,那么需要在参数之前加入两个连词线“--”,如下命令,则表示除了执行test子目录的测试脚本外,还要执行anothertest.js脚本。
npm run test -- anothertest.js # 等同于 mocha test/ anothertest.js #另外,npm本身也有个参数-s(注意小写的s),表示关闭npm本身的输出,只输出脚本产生的结果: # 输出npm命令头 npm run test # 不输出npm命令头 npm run -s test
4.10. npm bin
此命令显示Node模块的可执行脚本所在的目录(即.bin目录)
npm bin
4.11. npm adduser
此命令用于在npmjs.com注册一个用户。
npm adduser
Username: YOUR_USER_NAME
Password: YOUR_PASSWORD
Email: YOUR_EMAIL@domain.com
4.12. npm publish
此命令用于将当前模块发布到npmjs.com。执行之前,需要已经向npmjs.com注册了一个用户,也就是上方的npm adduser。
# 登录npmjs.com npm login # 发布当前模块 npm publish
4.13. npm prune
此命令用于检查当前项目的node_modules目录中,是否有package.json里面没有提到的模块,然后将所有这些模块输出在命令行。
npm prune
4.14. npm shrinkwrap
此命令用于锁定当前项目的依赖模块的版本,运行该命令后,会在当前项目的根目录下生成一个npm-shrinkwrap.json文件,内容是node_modules目录下所有已经安装的模块,以及它们的精确版本。
下次运行npm install命令时,npm发现当前目录下有npm-shrinkwrap.json文件,就会只安装里面提到的模块,且版本也会保持一致。
npm shrindwrap
5. scripts脚本最佳实践
5.1. npm-run-all
首先安装npm-run-all,这个模块用于运行多个scripts脚本命令。
npm install npm-run-all --save-dev
可以使用npm-run-all来直接进行多个脚本命令的执行:
- 继发执行
npm-run-all build:html build:js # 等同于 npm run build:html && npm run build:js
- 并行执行
npm-run-all --parallel watch:html watch:js # --parallel表示并行的意思,等同于 npm run watch:html & npm run watch:js
- 混合执行
npm-run-all clean lint --parallel watch:html watch:js # 等同于 npm-run-all clean lint npm-run-all --parallel watch:html watch:js
- 通配符
# 表示并行执行scipts中配置的以watch开头的命令 $ npm-run-all --parallel watch:*
5.2. start脚本
start脚本命令主要用于启动应用程序。
#比如如下的scripts脚本编写的start脚本如下: "scripts":{ "start": "npm-run-all --parallel dev serve" } npm start #等同于 npm run dev & npm run serve #如果start脚本没有配置,npm start命令默认执行下面的脚本 #前提是模块的根目录存在一个server.js文件 node server.js
5.3. dev脚本
此脚本主要用于规定开发阶段所要做的处理,比如构建网页资源等。
#比如如下的脚本中编写的dev脚本如下: "scripts":{ "dev": "npm-run-all dev:*", #将sass文件编译为css文件,并生成source map文件 "predev:sass": "node-sass --source-map src/css/hoodie.css.map --output-style nested src/sass/base.scss src/css/hoodie.css", #监视sass文件的变动,只要有变动,就自动将其编译为css文件 "dev:sass": "node-sass --source-map src/css/hoodie.css.map --watch --output-style nested src/sass/base.scss src/css/hoodie.css", } npm dev #等同于 npm run predev:sass && npm run dev:sass
5.4. server脚本
serve脚本命令用于启动服务。
#比如如下的脚本中编写的server脚本如下: "scripts":{ "serve": "live-server dist/ --port=9090" } npm run server #等同于 live-server dist/ --port=9090
上面的命令用于启动服务,使用的是live-server模块,将服务启动在9090端口,展示的内容为dist子目录下的index.html,live-server模块有三个功能:
- 启动一个HTTP服务器,展示指定目录的index.html文件,通过该文件加载各种网络资源,这是file://协议做不到的。
- 添加自动刷新功能。只要指定目录之中,文件有任何变化,它就会刷新页面。
- npm run serve命令执行以后,自动打开浏览器。
5.5. test脚本
test脚本命令用于执行测试。
#比如如下的脚本中编写的test脚本如下: "scripts":{ "test": "npm-run-all test:*", "test:lint": "sass-lint --verbose --config .sass-lint.yml src/sass/*" } npm test #等同于 npm run test:lint #也就是执行如下命令,运行lint脚本,检查脚本之中的语法错误: sass-lint --verbose --config .sass-lint.yml src/sass/*
5.6. prod脚本
prod脚本命令,规定进入生产环境时需要做的处理。
#比如如下的脚本中编写的prod脚本如下: "scripts":{ "prod": "npm-run-all prod:*", "prod:sass": "node-sass --output-style compressed src/sass/base.scss src/css/prod/hoodie.min.css" } npm run prod #等同于 npm run prod:sass #也就是执行如下命令,将sass文件转为css文件: node-sass --output-style compressed src/sass/base.scss src/css/prod/hoodie.min.css
5.7. pre-脚本和post-脚本
npm run为每条命令提供了pre-和post-两个钩子(hook)。
以npm run lint为例,执行这条命令之前,npm会先查看有没有定义prelint和postlint两个钩子,如果有的话,就会先执行npm run prelint,然后执行npm run lint,最后执行npm run postlint。
#如下的一个脚本: "scripts": { "lint": "eslint --cache --ext .js --ext .jsx src", "test": "karma start --log-leve=error karma.config.js --single-run=true", "pretest": "npm run lint", "posttest": "echo 'Finished running tests'" }
当我们执行npm test时,其执行顺序如下,如果执行过程出错,就不会执行排在后面的脚本。
- pretest
- test
- posttest
6. npm5新特性
6.1. 锁文件(lockfile)
npm5 新增了 package-lock.json 文件,在操作依赖时默认生成,用于记录和锁定依赖树的信息。
当我们首次执行 npm install 后,会默认创建 package-lock.json 文件,之后再次 install 将根据此文件中的记录进行安装。
如果对依赖进行了修改(新增依赖、npm update 或 npm install 指定不同版本的包),都将更新记录到此文件中。
实际上,npm5 新增的 package-lock.json 文件和之前通过 npm shrinkwrap 命令生成的 npm-shrinkwrap.json 文件的格式完全相同,文件内记录了版本,来源,树结构等所有依赖的 metadata。
最新的 npm5 在生成了 package-lock.json 之后,再运行 npm shrinkwrap 命令,会发现就是把 package-lock.json 重命名为 npm-shrinkwrap.json 而已。
旧版本的 npm-shrinkwrap.json 文件是不包括 devDependencies 的,而这次升级 npm5 后 devDependencies 将被包括进来,所以如果使用旧版时已有 npm-shrinkwrap.json 文件,这次升级后再次 install 时会把这些缺少的依赖加进去,人开发时要注意大家同步升级,避免产生新旧混用的情况。
6.1.1. package-lock.json和npm-shrinkwrap.json的使用场景
- 开发时提交和使用 package-lock.json 来保证不同环境、人员安装依赖的一致性。
- 发布包时如果有锁定的需求,可以用 npm shrinkwrap 命令把 package-lock.json 转为 npm-shrinkwrap.json 随包发布。
- 如果项目中已经在使用 npm-shrinkwrap.json,可以继续使用(但要注意从旧版本升级到 npm5 后 install 时会被更新),其优先级高于 package-lock.json,并且不会再被重复创建。
6.2. Git 依赖支持
新版本对 Git 依赖支持了通过 semver 版本号安装指定的版本。例如可以通过以下命令安装 Github 上的 chalk 1.0.0 版本:
npm install git+https://github.com/chalk/chalk.git#semver:1.0.0
6.3. 文件依赖优化
在之前的版本,如果将本地目录作为依赖来安装,将会把文件目录作为副本拷贝到 node_modules 中。而在 npm5 中,将改为使用创建 symlinks 的方式来实现(使用本地 tarball 包除外),而不再执行文件拷贝。这将会提升安装速度。
6.4. 缓存优化
新版本重写了整个缓存系统,缓存将由 npm 来全局维护不用用户操心,这点也是在向 yarn 看齐。升级新版后,用户基本没有手动操作 npm cache 的场景。npm cache clean 将必须带上 --force 参数才能执行,并且会收到警告。
6.5. registry 策略优化
npm config配置的registry优先级高于锁文件中记录的优先级;除非使用不同 scope 的包,不再支持不同的包使用不同的 registry。
7. 其他
7.1. npm安装慢解决方法
解决方法就是安装cnpm,通过如下命令进行安装:
npm install -g cnpm --registry=https://registry.npm.taobao.org
然后输入cnpm -v即可查看cnpm安装的版本。
如果出现命令不存在,则将cnpm.cmd(windows中,Linux为cnpm)所在的目录添加至环境变量中重新打开命令行即可。
7.2. npm install-g全局安装时报错
npm install packageName -g 即将下载的模块安装在 {prefix}/lib/node_modules/ 中,为 root 用户所有(而{prefix}通常是 /usr/ 或者 /usr/local]),当然我们可以通过git config list -l命令来查看全局安装路径,出于安全性的考虑和避免解析第三方的依赖模块时可以引发的权限错误,我们需要使用 sudo 命令来解决权限的问题,即sudo npm install -g
整理了一份思维导图,希望对大家有所帮助: