第二课前端模块包管理工具之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(管理器)。

核心概念

    1. 组件库下载 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到最高的中版本

案例二之锁包机制

  1. 如果这个项目是个脚手架,我们通过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

总结

  1. 在有脚手架的情况下,npm i 安装所有包的时候,会根据package.lock.json(锁定包的版本)进行安装仓库,防止包的版本混乱

  2. cnpm是不会管你的lock.json文件,直接根据npm进行安装的机制,迭代到最新中版本,并且不会更新两个json文件(所以可能出现问题)

  3. 安装组件库只会下载组件库的生产环境模块包、不会下载开发依赖包

  4. 模块包可以不通过npm的方式安装,直接添加到node_modules,因为前端模块读取方式本来就是直接读取的node_modules包,但是不建议这么操作,因为别的同事git下载的项目肯定没有该模块,就会报错

  5. 必须npm install 才会更新package.json和package.lock.json文件

  6. 自己开发的项目依赖一个组件库fulu-method@1.0.2, 又下载了一个组件库fulu-check,fulu-check也依赖一个组件库fulu-method@1.0.1,会以自己开发的项目fulu-method@1.0.2为主

node_modules找寻规则

    1. 从module paths数组中取出第一个目录作为查找基准
    1. 直接从目录中查找该文件,如果存在,则结束查找。如果不存在,则进行下一条查找
    1. 尝试添加.js、.json、.node后缀后查找,如果存在文件,则结束查找。如果不存在,则进行下一条。
    1. 尝试将require的参数作为一个包来进行查找,读取目录下的package.json文件,取得main参数指定的文件。
    1. 尝试查找该文件,如果存在,则结束查找。否则按第三条方式找寻下一个目录,
  • 6.如果当前文件夹下的node_modules都没有,则会按1-5规则找到外层node_modules,直到找到最外层的node_modules
    1. 都找不到就会报错

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

  • 搜索路径
  1. Node将试图去当前目录的node_modules文件夹里搜索。如果当前目录的node_modules里没有找到,Node会从父目录的node_modules里搜索,这样递归下去直到根目录。
  • 好处
  1. 使用package.json安装好之后,node_modules文件夹中没有版本信息,从而package.json可以删掉了。
  2. 移动/复制/打包项目比较简单,对于开发、部署都有好处
  3. 随意改代码。安装在node_modules里面的东西,你可以随便改,无需担心对其它项目的影响。在Java中使用maven管理项目时,如果想要定制某个库,就需要更改这个库的源代码,这时就需要把这个库的源代码复制到项目中,跟node_modules是一个道理。npm的设计者大概认为:前端都是经常修改库的源代码的。
  • 坏处
  1. 每次都需要安装依赖,费流量,网速慢时很费时间
  2. 浪费磁盘空间,每个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下载的模块包里面的源代码,并提供截图

目的

安装、下载以及使用模块包,熟悉模块构成目录

posted @ 2020-10-26 23:58  福小松  阅读(544)  评论(0编辑  收藏  举报