概念
module、library、package
模块(modulue):通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块或者主模块
库(library/lib):以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案
包(package):包含元数据的库,这些元数据包括:名称、描述、git主页、许可证协议、作者、依赖等
背景
commonJS的出现,使node环境下的js代码可以用模块更加颗粒度的划分,一个类、一个函数、一个对象、一个配置等等都可以作为一个模块,这种细粒度的划分,是开发大型应用的基石
为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等,一时间,在前端社区出现了大量的第三方库,这些库使用commonJs标准书写而成,非常容易使用。
然而,在下载使用这些第三方库的时候,遇到了难以处理的问题:
- 下载过程繁琐
- 进入官网或github主页
- 找到并下载对应版本
- 拷贝到工程的目录中
- 遇到同名的库,修改名称
- 如果该库依赖其他的库,还需要按照要求先下载其他的库
- 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
- 更新一个库极度麻烦
- 自己开发的库,如何在下一次开发使用
以上这些问题,就是包管理器工具要解决的问题
前端包管理器
npm
几乎可以这样认为,前端所有的包管理器都是基于npm的,目前,npm既是一个包管理器,也是其他包管理器的基石
npm:node package manager,即node包管理器,它运行在node环境中,让开发者用简单的方式完成包的查找、更新、卸载、上传等操作
npm之所以要运行在node环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能(为了保护用户隐私,js不允许操作本地文件),而node属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行node的计算机
npm的出现,弥补了node没有包管理器的缺陷,于是很快,node在安装文件中就内置了npm,安装好node就自动安装了npm,node环境还为npm提供了良好的支持,使得使用npm下载的包变得更加方便
npm的组成
- registry:入口
- 可以想象成一个巨大的数据库
- 第三方库的开发者,将自己的库按照npm的规范,打包上传到数据库中
- 使用者通过统一的地址下载第三方库
- 官网
- 查询包
- 注册、登录、管理个人信息
- CLI(Command Line Interface):命令行接口
- 安装好npm后,使用CLI来使用npm的各种功能
npm CLI
1、仓库设置
npm的官方registry服务器位于国外,导致下载时速度缓慢或者下载失败;淘宝提供了一个国内的registry地址,可以使用设置淘宝的镜像源地址为registry地址进行依赖的下载
设置淘宝镜像源
npm config set registry https://registry.npmmirror.com/
备注:淘宝镜像源的域名变更
查看registry
npm config get registry
2、安装方式
- 本地安装
npm install XXX
npm i XXX
- 本地安装的包出现在当前目录的`node_modules`目录中
- `node_modules`目录可能会异常庞大,不适合传输到生产环境,因此使用`.gitignore`文件来忽略该目录的内容
- 本地安装适用于大多数的包,他会在当前目录及其子目录中发挥作用,通常在项目的根目录中使用本地安装
- 安装一个包时,npm会自动管理依赖,将该包得依赖包下载到`node_modules`中
- 如果本地安装的包带有CLI,npm会将他的CLI脚本文件放置在`node_modules/.bin`下,使用 `npx 命令名` 即可调用
- 全局安装
npm install --global xxx
npm i -g xxx
全局安装的包放置在一个特殊的全局目录下,可以通过命令npm config get prefix查看该目录
全局安装的包并非所有工程可用,他仅提供全局的CLI工具
大部分情况下,都不需要全局安装,除非:
- 包的版本非常稳定,很少有大的更新
- 提供的CLI工具在各个工程中使用得非常频繁
- CLI工具仅为开发环境提供支持,而非部署环境
3、包配置
目前遇到的问题:
1、拷贝后的工程如何还原?
2、如何区分开发依赖和生产依赖?
3、如果自身的项目也是一个包,如何描述包的信息
以上这些问题都需要通过包的配置文件解决
配置文件
npm将每个使用npm的工程本身都看做一个包,包的信息需要通过一个名称固定的配置文件描述,配置文件的名称固定为package.json
可以手动创建一个package.json文件,也可以使用npm init可以生成一个package.json文件,该命令会引导填写包的相关信息,也可以使用npm init --yes 或者npm init -y生成一个默认配置的package.json文件
配置文件中可以描述大量的信息,包括:
- name
- version:版本
- description:包的描述
- homepage:官网地址
- author:包的作者,必须是有效的npm账户名,书写规范
account<email>,不正确的账号和邮箱就可能会导致发布包时失败 - mian:包的入口文件
package.json最重要的作用,是记录当前工程的依赖
- dependencies:生产环境的依赖包
- devDependencies:仅开发环境的依赖包
配置好依赖后,使用下面的命令就可以安装依赖
// 本地安装所有依赖 dependencies+devDependencies
npm install
npm i
//仅安装生产环境的依赖 dependencies
npm install --production
这样一来,代码移植就不是问题了,只需要移植源代码和package.json文件,不用移植node_modules目录,然后在移植之后通过命令即可恢复安装
//安装生产环境依赖
npm i --save xxx
npm i -S xxx
npm i XXX
//安装开发环境依赖
npm i --save-dev xxxx
npm i -D xxx
现在安装依赖默认保存到package.json,可以省略--save
4、包使用(查找规则)
使用nodejs导入模块时, eg:var = require('xxx')
if(如果模块路径是./或者../){
根据当前目录去查找你的本地代码文件
}else{
if(导入的模块是node的内置模块){
直接导入node的内置模块(fs、path等)
}else{
//首先在当前目录的以下位置寻找文件
if(node_modules/xxx.js存在){
找到该模块
}else if(node_modules/xxx/入口文件 存在){
找到该模块
}else{
返回上级目录按照同样方式查找
先找node_modules/XXX.js,再找node_modules/xxx/入口文件
如果到了顶级目录都无法找到文件,则抛出错误
}
}
}
入口文件按照以下规则确定:
1、查看导入包的package.json文件,读取main字段作为入口文件
2、若不包含main字段,则使用index.js作为入口文件
5、语义版本
版本号规则:
版本规范:主版本号.次版本号.补丁版本号
主版本号:仅当程序发生了重大变化时才增加,如新增了重要的功能、新增了大量的API,技术架构发生了重大变化
次版本号:仅当程序发生了一些小变化时才会增加,如新增了一些小功能,新增了一些辅助型的API
补丁版本号:仅当解决了一些bug或进行了一些局部优化时更新,如修复了某个函数的bug,提升了某个函数的运行效率
| 符号 | 含义 | 示例 | 示例描述 |
|---|---|---|---|
| '>' | 大于该版本 | >1.0.0 | 大于1.0.0版本 |
| >= | 大于等于该版本 | >=1.0.0 | 大于等于1.0.0版本 |
| < | 小于该版本 | <1.0.0 | 小于1.0.0版本 |
| <= | 小于等于该版本 | <=1.0.0 | 小于等于1.0.0版本 |
| = | 等于该版本 | =1.0.0 | 等于1.0.0版本 |
| - | 介于两版本之间 | 1.0.0-1.2.0 | 介于1.0.0和1.2.0之间 |
| x | 不固定的版本号 | 1.3.X | 只要保证主版本号是1,次版本号是3即可 |
| ~ | 主版本和次版本锁定,补丁版本可增 | ~1.3.6 | 保证主版本号是1,次版本号是3,补丁版本号大于等于6 |
| '^' | 主版本锁定,次版本和补丁版本可增 | ^1.4.7 | 报好着呢个主版本号是1,次版本号可以大于等于4,补丁版本号可以大于等于7 |
| * | 最新版本 | * | 始终安装最新版本 |
避免还原的差异
npm在安装包的时候,会自动生成一个package-lock.json文件,该文件记录了安装包时的确切依赖关系,移植工程时,如果移植了package-lock.json,恢复安装时,会按照package-lock.json文件中的确切依赖进行安装,最大限度的避免了差异
6、 npm脚本(npm scripts)
在开发过程中,我们可能会反复的使用很多第三方的CLI命令,这些命令繁杂难于记忆,所以npm支持了脚本,只需要在package.json中配置scripts字段,即可配置各种脚本名称,配置后,就可以通过npm run 脚本名称 来完成各种操作了,npm对常用的脚本名称做了简化,start、stop、test脚本名称是可以省略run,start脚本的默认值:node server.js
本地安装的依赖含有CLI,在node_modules/.bin目录下会出现该依赖的CLI命令,直接在终端中运行会报错,可以使用npx 命令来运行,eg:如果安装了nodemon,则可以使用npx nodemon index.js来运行,将CLI配置在脚本中,配置时可以省略npx,eg:"start:nodemon index.js
脚本中可以配置任何命令
"script":{
"mkdir": "mkdir src",
"open":"chrome https//www.xxx.com"
}
7、运行环境配置
代码运行的环境一般有三种
1、开发环境
2、测试环境
3、生产环境
有的时候,我们需要在代码中根据环境做出不同的处理,如何让node知道处于什么环境中,是极其重要的
通常我们使用如下的处理方式:
node有一个全局变量global,global有一个属性process,该属性是一个包含了运行node程序的计算机的很多信息的对象,其中一个信息是env,是一个对象,包含了计算机中所有的系统变量
通常,我们通过系统变量NODE_ENV的值来判断node程序处于何种环境,设置NODE_ENV有如下两种方式:
- 永久设置:在系统的环境变量中,添加NODE_ENV,使用时通过proces.env.NODE_ENV读取值
- 临时设置:在配置脚本时,使用set NODE_ENV=xxx(windows) 来设置环境变量后启动程序,eg:
"start":"set NODE_ENV=development && node index.js";不同操作系统设置环境变量的方式不同,mac和服务器:"start":"export NODE_ENV=development && node index.js";为了避免不同系统的设置方式差异,可以使用第三方库cross-env对环境变量进行设置,"start":"cross-env NODE_ENV=development node index.js"
在node中读取package.json文件中的配置,如果你导入的内容是一个json数据,导入时node会将该json数据转成一个对象
8、 npm命令
//安装
//安装精确版本
npm install --save-exact xxx
npm i -E xxx
//安装指定版本
npm i xxx@版本号
//查询
//查看包安装路径
npm root [-g]
//查看包信息,view别名:v、info、show
npm view 包名 [子信息]
//查询安装包
npm list [-g] [--depth=依赖深度]
//更新
//检查哪些包需要更新
npm outdated
//更新包,update别名:up、upgrade
npm update [-g] [包名]
//卸载
//unstall别名:remove、rm、r、un、unlink
npm uninstall [-g] 包名
//npm配置
//查询目前生效的各种配置
npm config ls [-l] [--json]
//获取某个配置项
npm config get 配置项
//设置某个配置项
npm config set 配置项 值
//移除某个配置
npm config delete 配置项
9、发布包
准备工作:
1、移除淘宝镜像源
2、npm官网注册账号并完成邮箱认证
3、使用npm login登录(可以使用npm whoami 查看当前登录的账号,使用npm logout退出登录)
4、创建工程根目录
5、npm init进行初始化
发布:
1、开发
2、确定版本
3、npm publish 完成发布
yarn
yarn使用的是npm的registry
过去npm存在的问题
- 依赖包嵌套层级深(过去,npm的依赖是嵌套的,在windows中这是一个极大的问题,windows的目录层级不能过深,默认情况下,目录路径最多支持256个字符)
- 下载速度慢
- 依赖嵌套,所以下载是串行的,导致宽带资源未被完全利用
- 多个相同版本包被重复下载
- 控制台输出繁杂,错误信息定位难
- 工程移植问题(当时的npm无lock文件,版本依赖模糊,导致工程移植后,依赖的版本不一致)
yarn如何解决过去npm存在的问题
- 使用扁平目录结构
- 并行下载,不重复下载,使用本地缓存
- 控制台输出关键信息
- yarn-lock记录确切的版本
yarn的优化内容:
- 增加了某些功能强大的命令
- 让既有的命令更加语义化
- 本地安装的CLI工具可以直接使用yarn直接启动,eg:
yarn nodemon index.js - 把全局安装的目录作为一个普通工程,生成package.json,便于全局安装移植
yarn的出现促进了npm的发展,npm借鉴yarn对自身进行了优化,几乎解决了上面的问题:
- 目录扁平化
- 并行下载,本地缓存(npm-cache,清空缓存:
npm cache clean) - package-lock记录确切依赖
- 增加了大量的命令别名
- 内置了npx,可启动本地的CLI
- 简化了控制台的输出
yarn的核心命令
//初始化
yarn init [--yes/-y]
//添加指定包
yarn [global] add xxx [--dev/-D] [--exact/-E]
//安装package.json中所有的依赖
yarn install [--production/-prod]
//脚本和本地CLI(start、stop、test可以省略run)
yarn run 脚本名/CLI名
//查询
//查看bin目录
yarn [global] bin
//查询包信息
yarn info 包名 [子字段]
//列举已经安装的依赖
yarn [global] list [--depth=依赖深度]
//更新
//查询需要更新的包
yarn outdated
//更新包
yarn [global] upgrade [包名]
//卸载
yarn remove 包名
yarn 增加的功能强大的命令
//验证package.json文件的依赖记录和lock文件是否一致,防止篡改
yarn check
// 检查本地安装的包有哪些漏洞
yarn audit
//为什么安装了这个包,哪些包用到了它
yarn why
//一步完成安装脚手架和搭建工程
yarn create
//示例
yarn create react-app my-app
//等于以下两个命令
yarn global add create-react-app
create-react-app my-app
cnpm
因为以前的npm未提供修改registry的功能,所以淘宝提供了CLI工具cnpm,支持除了npm publish以外的其他所有命令,只不过连接的是淘宝镜像源
pnpm
pnpm的优势
- 安装效率高于npm和yarn
- 极其简洁的node_modules目录
- 避免了开发时使用间接依赖的问题
- 能极大的降低磁盘空间占用
安装和使用
npm i -g pnpm
使用时,只需要把npm替换成pnpm即可
如果要执行本地的CLI,可以使用pnpx,和npx功能完全一样,唯一不同的区别是,在使用pnpx执行一个需要安装的命令时,会使用pnpm进行安装
补充:npx或pnpx在执行本地CLI时,没有该命令,npx/pnpx会自动的、临时的安装该依赖包,再运行命令;比如:npx mocha执行本地mocha命令时,如果没有mocha,则npx会自动的、临时的安装mocha,安装好后,自动运行mocha命令
pnpm的原理
1、同yarn和npm一样,pnpm依然使用缓存来保存已经安装过的包,以及使用pnpm-lock.yaml来记录详细的依赖版本
2、不同于yarn和npm,pnpm使用符号链接和硬链接的做法来放置依赖,从而规避了从缓存中拷贝文件的时间,使得安装和卸载的速度更快
3、由于使用了符号链接和硬链接,pnpm可以规避windows操作系统路径过长的问题,因为,它选择使用树形的依赖结果,有着几乎完美的依赖管理。也因为如此,项目只能使用直接依赖,不能使用间接依赖
补充知识点
HCI:Human-Computer Interaction,人机交互,研究和设计人类与计算机之间交互的学科
HCI的分类:
- CLI:Command Line Interfalce
- GUI:Graphical user Interface,图形用户界面
- 触摸屏
- 语音交互
- 虚拟现实VR以及增强现实AR
- 脑机接口
- 可穿戴设备
文件的本质
在操作系统中,文件的本质是一个指针,只不过指向的不是内存地址,而是一个外部存储地址(硬盘、u盘、网络),当我们删除文件时,删除的实际上是指针,无论删除多大的文件,速度都非常快
文件的拷贝
复制一个文件时,是将该文件的指针指向的内容进行复制,然后产生一个新文件指向新的内容
硬链接 hard link
将一个文件A指针复制到另一个文件B指针中(类似于对象的赋值),文件B就是文件A的硬链接
通过硬链接,不会产生额外的磁盘占用,并且,两个文件都能找到相同的磁盘内容,硬链接没有数量限制,可以为同一个文件产生多个硬链接
cmd中使用命令mklink /h 链接名称 目标文件创建硬链接,由于文件目录不存在文件内容,所以文件夹(目录)不可以创建硬链接,在windows中,通常不要跨越盘符创建硬链接
符号链接(软链接) symbol link
如果为某个文件或者文件夹A创建符号链接B,则B指向A
创建符号链接:mklink /d 链接名称 目标文件,/d:创建目录的符号链接
符号链接和硬链接的区别
1、硬链接仅能链接文件,符号链接可以链接目录和文件 2、硬链接在链接完成后仅和文件内容相关联,和之前链接的文件没有任何关系;符号链接始终和之前链接的文件关联,和文件内容不直接相关
快捷方式
快捷方式类似于符号链接,是windows系统早期就支持的链接方式
它不仅仅是一个指向其他文件或目录的指针,其中还包括了各种信息,如权限、兼容性启动方式等其他各种属性
由于快捷方式是windows系统独有的,在跨平台的应用中一般不会使用
node环境对硬链接和符号链接的处理
- 硬链接:硬链接是一个实实在在的文件,node不对其做任何特殊处理,因为无从知晓该文件是不是一个硬链接
- 符号链接:由于符号链接指向的是另一个文件或目录,当node执行符号链接下的js文件时,会使用原始路径(可自行借助__dirname验证是否是原始路径)