第二课前端模块包管理工具之npm
环境搭建
安装nodejs
安装vscode
npm
npm的思路大概是这样的
- 使用 npm publish 把代码提交到 repository 上,分别取名 jquery、bootstrap 和 underscore
- 社区里的其他人如果想使用这些代码,运行 npm install就把 jquery、bootstrap 和 underscore 写到 package.json 里
- 在package.json写入包名以及版本,然后使用npm install也可以进行安装
- 下载完的代码出现在 node_modules 目录里,就可以随意使用了。
这些可以被使用的代码被叫做「包」(package),这就是 npm名字的由来:Node Package(包) Manager(管理器)。
核心概念
-
- 组件库下载 2.组件库上传 3.稳定成熟的版本管理
npm 中的依赖包
这里只说我们常用的两个依赖包 dependenices 和 devDependenices,其它的一些依赖包只有作为包的发布者才会用到,需要的小伙伴自行查看文档。
package.json 简要介绍
- name - 包名;
- version - 包的版本号;
- description - 包的描述;
- homepage - 包的官网 url;
- author - 包的作者姓名;
- contributors - 包的其他贡献者姓名;
- dependencies - 依赖包列表。如果依赖包没有安装,npm 会自动将依赖包安装在 node_module 目录下;
- devDependencies:开发环境下,项目所需依赖。
- repository - 包代码存放的地方的类型,可以是 git 或 svn,git 可在 Github 上;
- main - main 字段指定了程序的主入口文件,require('moduleName') 就会加载这个文件。这个字段的默认值是模块根目录下面的 index.js;
- keywords - 关键字;
- bin:用来指定各个内部命令对应的可执行文件的路径
dependenices
-
通过命令npm install/i packageName -S/--save把包装在此依赖项里。如果没有指定版本,直接写一个包的名字,则安装当前npm仓库中这个包的最新版本。如果要指定版本的,可以把版本号写在包名后面,比如npm i vue@3.0.1 -S。
-
从npm 5.x开始,可以不用手动添加-S/--save指令,直接执行npm i packageName把依赖包添加到dependencies中去。
-
不仅开发环境能使用,生产环境也能使用
-
例如react、antd
devDependenices
- 只会在开发环境下依赖的模块,生产环境不会被打入包内
- 有一些包有可能你只是在开发环境中用到,例如你用于检测代码规范的 eslint ,用于进行测试的 jest ,用户使用你的包时即使不安装这些依赖也可以正常运行,反而安装他们会耗费更多的时间和资源,所以你可以把这些依赖添加到 devDependencies 中,这些依赖照样会在你本地进行 npm install 时被安装和管理,但是不会被安装到生产环境:
- 例如less-loader webpack
cnpm npm yarn的比较
以安装vue-cli为例,
耗时从少到多:
cnpm:
cnpm install vue-cli --save
用时:3s
yarn:
yarn add vue-cli
用时:31.12s
npm:
npm install vue-cli --save
用时:89.482s
- yarn
- cnpm
- npm
npm
- 5.0之前版本很多问题,下载速度慢,不能锁包
- 主流,社区庞大,各大网站库都是使用npm进行安装
- 成熟、稳定
- 完善了很多问题(锁包、本地缓存),慢慢接近yarn
cnpm
优点:
- cnpm是个中国版的npm,是淘宝定制的 cnpm,下载速度极快
缺点:
- 每隔10分钟去刷新npm库同步到cnpm
- 只会读取取package.json,不会读取package.lock.json
- 安装也不会生成或改变package.lock.json
yarn
优点:
-
(1)并行安装:无论 npm 还是Yarn在执行包的安装时,都会执行一系列任务。npm是按照队列执行每个package,也就是说必须要等到当前package安装完成之后,才能继续后面的安装。而 Yarn 是并行执行所有任务,提高了性能。
-
(2)离线模式:如果之前已经安装过一个软件包,用Yarn再次安装时直接从缓存中获取,就不用像npm那样再从网络下载了。
-
集成了npm基本所有的优点
npm和yarn命令对比
总结:
如果是安装层面更建议使用yarn进行安装模块包,速度快,稳定性强,只要对里面各种机制熟练使用,就可以把三者用得如鱼得水,不然就会出各种难以预料的问题
npm包的安装方式
// 开发环境和生产环境都能使用
npm install fulu-check --save(-S)
// 以下几行添加到package.json文件中
"dependencies": {
"fulu-check": "^1.0.0"
}
// 生产环境不会被打入包内
npm install webpack --save-dev(-D)
"devDependencies": {
"webpack": "^4.8.1"
}
-g 全局安装
- ^字符,告诉npm,安装主版本等于4的任意一个版本即可
- 现在运行npm进行安装,npm将安装lodash的主版本为4的最新版,可能是 lodash@4.25.5(@是npm约定用来确定包名的指定版本的)
- 理论上,次版本号的变化并不会影响向后兼容性。因此,安装最新版的依赖库应该是能正常工作的,而且能引入自4.17.4版本以后的重要错误和安全方面的修复。
- 即使不同的开发人员使用了相同的package.json文件,在他们自己的机器上也可能会安装同一个库的不同种版本,这样就会存在潜在的难以调试的错误和“在我的电脑上…”的情形
- 总而言之,npm是一个成熟、稳定、并且有趣的包管理
分为三个版本
- 主版本号(A):当你做了不兼容的 API 修改,0表示处于开发阶段;
- 次版本号(B):当你做了向下兼容的功能性新增;
- 修订号(C):当你做了向下兼容的问题修正。
~允许小版本迭代
- 如果有缺省值,缺省部分任意迭代;
- 如果没有缺省值,只允许补丁即修订号(Patch)的迭代
eg.:
- ~1.2.3:>=1.2.3 <1.3.0
- ~1.2:>=1.2.0 < 1.3.0(相当于1.2.x)
- ~1:>=1.0.0 <2.0.0(相当于1.x)
- ~0.2.3:>=0.2.3 <0.3.0
- ~0.2:>=0.2.0 <0.3.0(相当于0.2.x)
- ~0:>=0.0.0 <1.0.0(相当于0.x)
^允许大版本迭代
- 允许从左到右的第一段不为0那一版本位+1迭代(左闭右开);
如果有缺省值,且缺省值之前没有不为0的版本位,则允许缺省值的前一位版本+1迭代
eg.: - ^1.2.3:>=1.2.3 <2.0.0
- ^0.2.3:>=0.2.3 <0.3.0
- ^0.0.3:>=0.0.3 <0.0.4
- ^1.2.x:>=1.2.0 <2.0.0
- ^0.0.x:>=0.0.0 <0.1.0
- ^0.0:>=0.0.0 <0.1.0
- ^1.x:>=1.0.0 <2.0.0
- ^0.x:>=0.0.0 <1.0.0
每个npm包都有一个package.json,如果要发布包的话,package.json里面的version字段就是决定发包的版本号了。
version字段是这样一个结构: ‘0.0.1’,是有三位的版本号。分别是对应的version里面的:major, minor, patch。
也就是说当发布大版本的时候会升级为 1.0.0,小版本是0.1.0,一些小修复是0.0.2。
锁定(控制)版本
package-lock.json或是yarn.lock。
在npm的版本>=5.1的时候,package-lock.json文件是自动打开的,意味着会自动生成,
package-lock.json(官方文档)可以理解为/node_modules文件夹内容的json映射,并能够感知npm的安装/升级/卸载的操作。可以保证在不同的环境下安装的包版本保持一致。听上去很不错哈,实际使用中,大部分它的表现确实不错,可是如上述问题:我手动修改了package.json文件内依赖的版本,package-lock.json就没那么聪明(至少目前是,未来会不会变聪明就不可知了),且不会变化。
如果你真的想保证你的包版本在各个环境都是一样的话,请修改下package.json中的依赖,去掉默认前面的^,当然这样的话,你就没法自动享受依赖包小版本的修复了,问题来了,在什么情况下选择哪一种呢?
- 最后:rm -rf node_modules/ && npm install大法在你使用package-lock的情况下,请更换为:rm -rf node_modules && rm -rf package-lock.json && npm install。
package,json版本管理
案例1
npm install fulu-check 都可以进行更新
package.json
"dependencies": {
"fulu-check": "^1.0.8",
}
package.lock.json
"fulu-check": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/fulu-check/-/fulu-check-1.0.8.tgz",
"integrity": "sha512-fdvcBrGQELHo1+TMT1IKSEWMPjuhjJTJQD7WKx5GTTsjHoKyzFihiK+LGgewEa9boHKYj5hm37ejud++lO+n2A=="
},
node_modules
// node_modules版本是1.0.8
{
...,
"name": "fulu-check",
"version": "1.0.8",
...
}
- 结论:使用npm安装会同步更新package.json和package.lock.json到最高的中版本
案例二之锁包机制
- 如果这个项目是个脚手架,我们通过package.json来npm安装模块包,试试效果
- 将fulu-check的package.json版本重置到1.0.1,package.lock.json版本设置到1.0.2
- 删除node_modules的fulu-check
- npm install
package.json
"dependencies": {
"fulu-check": "^1.0.1",
}
package.lock.json
"fulu-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fulu-check/-/fulu-check-1.0.2.tgz",
"integrity": "sha512-H2v/0IeEjrU5Xja7A6cLuD8C+LrCV9mILBAlK2bNPj/Z/UDlLZqhna9YkRktVXxLG568yc71xz6hLkl3iH3cew=="
},
node_modules版本也是1.0.2
node_modules
// node_modules版本是1.0.2
{
...,
"name": "fulu-check",
"version": "1.0.2",
...
}
如果通过cnpm
package.json
"dependencies": {
"fulu-check": "^1.0.1",
}
package.lock.json
"fulu-check": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/fulu-check/-/fulu-check-1.0.2.tgz",
"integrity": "sha512-H2v/0IeEjrU5Xja7A6cLuD8C+LrCV9mILBAlK2bNPj/Z/UDlLZqhna9YkRktVXxLG568yc71xz6hLkl3iH3cew=="
},
node_modules
// node_modules版本是1.0.8
{
...,
"name": "fulu-check",
"version": "1.0.8",
...
}
- 结论:在脚手架使用npm install 读取的是package.lock.json(锁包机制),如果使用cnpm则版本会更新到当前库的最高中级版本
案例三之本地项目安装组件库
要下载的组件库fulu-check的package.json依赖项
"dependencies": {
"fulu-method": "^1.0.1"
},
"devDependencies": {
"react-scroll": "^1.8.1"
},
本地项目package.json
"fulu-check": "^1.0.11",
本地项目node_modules
存在fulu-check、fulu-methd两个文件夹,却没有react-scroll文件夹
- 结论:安装组件库只会下载组件库的生产环境模块包、不会下载开发依赖包
案例四之不安装直接使用
import { dateFormat } from 'fulu-method';
// 可以成功打印
console.log(dateFormat, 776655)
- 结论:模块包可以不通过npm的方式安装,直接添加到node_modules
总结
-
在有脚手架的情况下,npm i 安装所有包的时候,会根据package.lock.json(锁定包的版本)进行安装仓库,防止包的版本混乱
-
cnpm是不会管你的lock.json文件,直接根据npm进行安装的机制,迭代到最新中版本,并且不会更新两个json文件(所以可能出现问题)
-
安装组件库只会下载组件库的生产环境模块包、不会下载开发依赖包
-
模块包可以不通过npm的方式安装,直接添加到node_modules,因为前端模块读取方式本来就是直接读取的node_modules包,但是不建议这么操作,因为别的同事git下载的项目肯定没有该模块,就会报错
-
必须npm install
才会更新package.json和package.lock.json文件 -
自己开发的项目依赖一个组件库fulu-method@1.0.2, 又下载了一个组件库fulu-check,fulu-check也依赖一个组件库fulu-method@1.0.1,会以自己开发的项目fulu-method@1.0.2为主
node_modules找寻规则
-
- 从module paths数组中取出第一个目录作为查找基准
-
- 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找
-
- 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
-
- 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
-
- 尝试查找该文件,如果存在,则结束查找。否则按第三条方式找寻下一个目录,
- 6.如果当前文件夹下的node_modules都没有,则会按1-5规则找到外层node_modules,直到找到最外层的node_modules
-
- 都找不到就会报错
npm相同依赖安装规则
node_modules
-- foo
---- lodash@version1
-- bar
---- lodash@version2
假设 version1 和 version2 是兼容版本,则经过 dedupe 会成为下面的形式:
node_modules
-- foo
-- bar
-- lodash(保留的版本为兼容版本)
假设 version1 和 version2 为非兼容版本,则后面的版本保留在依赖树中:
node_modules
-- foo
-- lodash@version1
-- bar
---- lodash@version2
测试
node_modules层级找寻,上一层项目的node_modules,里面一个项目template-pro
// 外层文件夹下的node_modules下的fulu-check的dateFormate.js
export default function dateFormat(date, fmt) {
return 1231231235555;
}
// template-pro文件夹下的node_modules下的fulu-check的dateFormate.js
export default function dateFormat(date, fmt) {
return 66666;
}
// 引用
import dateFormat from 'fulu-method/dateFormat';
componentWillMount() {
console.log(dateFormat()); // 打印66666
}
// 删除template-pro文件夹下的node_modules下的fulu-check,则会读取外层文件夹的node_modules,打印1231231235555
webpack配置
- modules配置项不给的话默认的配置项是modules: ['node_modules'] ,正是由于node_modules找寻机制会浪费时间,对我们项目用处也不大,通常可以修改配置,让他只读取当前项目目录(join(__dirname, 'node_modules')),就不需要默认使用全局搜索
resolve: {
...
modules: [
join(__dirname, directory.build.envName),
join(__dirname, 'node_modules'),
'node_modules', // 为了演示node_modules的找寻机制
],
},
scripts
什么是 npm script 脚本?
在生成的 package.json 文件中,有一个 scripts 对象,在这个对象中,npm 允许使用 scripts 字段定义脚本命令。
"scripts": {
"start": "cross-env electronMode=dev node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --mode development --config webpack.config.dev.js",
"doc": "styleguidist server",
"doc-build": "styleguidist build",
"test": "jest",
"release": "cross-env operation=separateCSS node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --mode production --config webpack.config.prod.js --colors --progress",
"deploy": "npm run release && deploy-client"
}
原理
-
我们每次在运行 scripts 中的一个属性时候(npm run),**实际系统都会自动新建一个shell(一般是Bash),在这个shell里面执行指定的脚本命令。因此 凡是能在 shell 中允许的脚本,都可以写在npm scripts中。
-
scripts 对象中每一个属性,对应一段脚本。比如,test 命令对应的脚本是 node test.js。命令行下使用 npm run 命令,就可以执行这段脚本。
-
npm run如果不加任何参数,直接运行,会列出package.json里面所有可以执行的脚本命令(script字段里面的内容)。
.bin
- 此目录存储您的项目所依赖的所有可执行文件
- 安装模块包之后,如果package.json里有bin的配置项,这个shell会将当前项目的可执行依赖目录(即node_modules/.bin)添加到环境变量path中,当执行之后再恢复原样。就是说脚本命令中的依赖名会直接找到node_modules/.bin下面的对应脚本,所以scripts字段里面调用命令时不用加上路径
常用规则
- npm start、npm stop、npm test
- 正常情况下,npm 脚本是用户自己定义。不常用的命令需要run
"start": "node server.js"
"release": "cross-env operation=separateCSS node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --mode production --config webpack.config.prod.js --colors --progress",
- npm start
- npm run release
执行顺序
npm 脚本执行多任务分为两种情况
- 并行任务(同时的平行执行),使用&符号
$ npm run script1.js & npm run script2.js
- 串行任务(前一个任务成功,才执行下一个任务),使用 && 符号
$ npm run script1.js && npm run script2.js
node_modules
- 搜索路径
- Node将试图去当前目录的node_modules文件夹里搜索。如果当前目录的node_modules里没有找到,Node会从父目录的node_modules里搜索,这样递归下去直到根目录。
- 好处
- 使用package.json安装好之后,node_modules文件夹中没有版本信息,从而package.json可以删掉了。
- 移动/复制/打包项目比较简单,对于开发、部署都有好处
- 随意改代码。安装在node_modules里面的东西,你可以随便改,无需担心对其它项目的影响。在Java中使用maven管理项目时,如果想要定制某个库,就需要更改这个库的源代码,这时就需要把这个库的源代码复制到项目中,跟node_modules是一个道理。npm的设计者大概认为:前端都是经常修改库的源代码的。
- 坏处
- 每次都需要安装依赖,费流量,网速慢时很费时间
- 浪费磁盘空间,每个node_modules中包含的工具很多,动辄1 200M
// 快速完全删除node_modules的方法:
npm install rimraf -g
// 进入所需删除的node_modules文件夹的位置
rimraf node_modules
node_modules层级找寻测试
dva文件夹下面安装模块包,以及一个my-app的新项目(里面也安装模块包),使用my-app去引用fulu-check
// dva文件夹下的node_modules下的fulu-check的dateFormate.js
export default function dateFormat(date, fmt) {
return 1231231235555;
}
// my-app文件夹下的node_modules下的fulu-check的dateFormate.js
export default function dateFormat(date, fmt) {
return 66666;
}
// 引用
import dateFormat from 'fulu-method/dateFormat';
componentWillMount() {
console.log(dateFormat()); // 打印66666
}
// 删除my-app文件夹下的node_modules下的fulu-check,则会读取dva文件夹的node_modules
console.log(dateFormat()); // 打印1231231235555
npm库的发布
-
1.注册并登录npm账户
-
2.npm init
-
3.依次按提示填入包名、版本、描述、github地址、关键字、license等
-
4.package.json的main参数写入要导出的文件地址(index.js)
这个js导出我们的公用组件库、公用函数 -
5.npm publish
-
6.npm install
-
注意
发布的代码需要编译后的符合commonjs规范的代码,这样别人不需要编译也能运行,而且一般webpack配置是会排除node_modules文件夹里的库,如果浏览器不支持es6语法而组件库写了,项目会报错
课后作业
1.使用之前的脚手架项目安装fulu-check包
2.使用模块包里的checkTelNum函数手机号码的验证
-
初级版 直接传入手机号,弹出是否验证通过
-
进阶版 通过按钮点击获取文本框的数据,进行验证弹出
3. 查看今天讲课内容的package.json,package.lock.json.以及node_modules下载的模块包里面的源代码,并提供截图
目的
安装、下载以及使用模块包,熟悉模块构成目录