Javascript第八篇,NodeJs第二篇,注重Node后端开发。
# npm
npm
是 Node.js 标准的软件包管理器。
在 2017 年 1 月时,npm 仓库中就已有超过 350000 个软件包,这使其成为世界上最大的单一语言代码仓库,并且可以确定几乎有可用于一切的软件包。
它起初是作为下载和管理 Node.js 包依赖的方式,但其现在也已成为前端 JavaScript 中使用的工具。
npm
可以管理项目依赖的下载。
如果项目具有 package.json
文件,则通过运行npm install 安装
它会在 node_modules
文件夹(如果尚不存在则会创建)中安装项目所需的所有东西。
也可以通过运行以下命令安装特定的软件包
npm install package-name
通常会在此命令中看到更多标志:
--save
安装并添加条目到package.json
文件的 dependencies。--save-dev
安装并添加条目到package.json
文件的 devDependencies。
区别主要是,devDependencies
通常是开发的工具(例如测试的库),而 dependencies
则是与生产环境中的应用程序相关
更新软件包与安装类似,只是命令不同
npm update
package.json 文件支持一种用于指定命令行任务(可通过使用以下方式运行)的格式
npm run <task-name>
例如
{
"scripts": {
"start-dev": "node lib/server-development",
"start": "node lib/server-production"
},
}
{
"scripts": {
"watch": "webpack --watch --progress --colors --config webpack.conf.js",
"dev": "webpack --progress --colors --config webpack.conf.js",
"prod": "NODE_ENV=production webpack -p --config webpack.conf.js",
},
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# npx
npx
可以运行使用 Node.js 构建并通过 npm 仓库发布的代码
npx
是一个非常强大的命令,从 npm 的 5.2 版本(发布于 2017 年 7 月)开始可用
npx
的另一个重要的特性是,无需先安装命令即可运行命令
这非常有用,主要是因为:
- 不需要安装任何东西。
- 可以使用 @version 语法运行同一命令的不同版本。
npx的典型应用场景有
- 运行
vue
CLI 工具以创建新的应用程序并运行它们:npx @vue/cli create my-vue-app
。 - 使用
create-react-app
创建新的React
应用:npx create-react-app my-react-app
。
当被下载完,则下载的代码会被擦除。
# npm命令集
本地npm包相关
npm outdated 检查本地npm包是否有过期包
npm ci: 使用package-lock.json安装本地依赖
npm rebuild: 必须使用新的二进制文件重新编译所有 C++ 插件
npm docs:
npm包发布相关
npm star/unstar
npm team:
npm publish:
npm deprecate: 此命令将更新 npm 注册表中指定包所对应的数据条目, 为尝试安装它的所有人提示版本作废的警告信息
其他
npm ping: Ping 已配置的或给定的 npm 注册表地址并进行身份验证。 如果 ping 执行成功,则会输出类似下面的内容
npm config:
npm repo: 此命令尝试猜测指定包的源码仓库的 URL ,然后再使用 --browser
配置参数打开它。 如果没有提供包名称,它将在当前文件夹中搜索package.json
文件, 并使用其 name
属性的值
# pnpm、yarn、cnpm、npm的区别
pnpm 本质上就是一个包管理器,这一点跟 npm/yarn 没有区别,但它作为杀手锏的两个优势在于:
- 包安装速度极快;
- 磁盘空间利用非常高效
速度
pnpm,在绝多大数场景下,包安装的速度都是明显优于 npm/yarn,速度会比 npm/yarn 快 2-3 倍
yarn 有 PnP 安装模式
(https://classic.yarnpkg.com/en/docs/pnp/)吗?直接去掉 node_modules,将依赖包内容写在磁盘,节省了 node 文件 I/O 的开销,这样也能提升安装速度
支持mono repo
随着前端工程的日益复杂,越来越多的项目开始使用 monorepo。之前对于多个项目的管理,我们一般都是使用多个 git 仓库,但 monorepo 的宗旨就是用一个 git 仓库来管理多个子项目,所有的子项目都存放在根目录的packages
目录下,那么一个子项目就代表一个package
。如果你之前没接触过 monorepo 的概念,建议仔细看看这篇文章(https://www.perforce.com/blog/vcs/what-monorepo)以及开源的 monorepo 管理工具lerna
,项目目录结构可以参考一下 babel 仓库
(https://github.com/babel/babel)。
pnpm 与 npm/yarn 另外一个很大的不同就是支持了 monorepo,体现在各个子命令的功能上,比如在根目录下 pnpm add A -r
, 那么所有的 package 中都会被添加 A 这个依赖,当然也支持 --filter
字段来对 package 进行过滤
高效利用磁盘空间
pnpm 内部使用基于内容寻址
的文件系统来存储磁盘上所有的文件,这个文件系统出色的地方在于
不会重复安装同一个包。用 npm/yarn 的时候,如果 100 个项目都依赖 lodash,那么 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。但在使用 pnpm 只会安装一次,磁盘中只有一个地方写入,后面再次使用都会直接使用 hardlink
即使一个包的不同版本,pnpm 也会极大程度地复用之前版本的代码。举个例子,比如 lodash 有 100 个文件,更新版本之后多了一个文件,那么磁盘当中并不会重新写入 101 个文件,而是保留原来的 100 个文件的 hardlink
,仅仅写入那一个新增的文件
依赖管理
npm install 的原理:
主要分为两个部分, 首先,执行 npm/yarn install之后,包如何到达项目 node_modules 当中
。其次,node_modules 内部如何管理依赖
。
执行命令后,首先会构建依赖树,然后针对每个节点下的包,会经历下面四个步骤:
- 1. 将依赖包的版本区间解析为某个具体的版本号 - 2. 下载对应版本依赖的 tar 包到本地离线镜像 - 3. 将依赖从离线镜像解压到本地缓存 - 4. 将依赖从缓存拷贝到当前目录的 node_modules 目录
然后,对应的包就会到达项目的node_modules
当中。
在 npm1
、npm2
中呈现出的是嵌套结构,如果不同的依赖包有着相同包的不同版本,会出现以下问题:
- 依赖层级太深,会导致文件路径过长的问题,尤其在 window 系统下。
- 大量重复的包被安装,文件体积超级大。比如跟 foo 同级目录下有一个baz,两者都依赖于同一个版本的lodash,那么 lodash 会分别在两者的 node_modules 中被安装,也就是重复安装。
- 模块实例不能共享。比如 React 有一些内部变量,在两个不同包引入的 React 不是同一个模块实例,因此无法共享内部变量,导致一些不可预知的 bug。安全性**
从npm3开始,以及yarn中,都着手来通过扁平化依赖
的方式来解决这个问题
所有的依赖都被拍平到node_modules
目录下,不再有很深层次的嵌套关系。这样在安装新的包时,根据 node require 机制,会不停往上级的node_modules
当中去找,如果找到相同版本的包就不会重新安装,解决了大量包重复安装的问题,而且依赖层级也不会太深。
但是铺平的node_modules依然有很多问题:
- 依赖结构的不确定性。
- 扁平化算法本身的复杂性很高,耗时较长。
- 项目中仍然可以非法访问没有声明过依赖的包
第一个问题直接导致了 lock 文件
的诞生,无论是package-lock.json
(npm 5.x才出现)还是yarn.lock
,都是为了保证 install 之后都产生确定的node_modules
结构
不同于npm/yarn,使用pnpm安装包后,会在node_modules下会生成包的软连接,有助于快速找到安装了哪些包
同时,所有的包都放在.pnpm文件夹下,按照.pnpm
,.pnpm
目录下虽然呈现的是扁平的目录结构,但仔细想想,顺着软链接
慢慢展开,其实就是嵌套的结构。这样将包本身
和依赖
放在同一个node_module
下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙
非法访问的问题
在npm/yarn中,如果 A 依赖 B, B 依赖 C,那么 A 就算没有声明 C 的依赖,由于有依赖提升的存在,C 被装到了 A 的node_modules
里面,那我在 A 里面是可以用 C的,并且跑起来也没有问题。
但是当包依赖变化时, 如果 B 更新之后,可能不需要 C 了,那么安装依赖的时候,C 都不会装到node_modules
里面,A 当中引用 C 的代码直接报错。还有一种情况,在 monorepo 项目中,如果 A 依赖 X,B 依赖 X,还有一个 C,它不依赖 X,但它代码里面用到了 X。由于依赖提升的存在,npm/yarn 会把 X 放到根目录的 node_modules 中,这样 C 在本地是能够跑起来的,因为根据 node 的包加载机制,它能够加载到 monorepo 项目根目录下的 node_modules 中的 X。但试想一下,一旦 C 单独发包出去,用户单独安装 C,那么就找不到 X 了,执行到引用 X 的代码时就直接报错了。
这些,都是依赖提升潜在的 bug。如果是自己的业务代码还好,试想一下如果是给很多开发者用的工具包,那危害就非常严重了。
npm 也有想过去解决这个问题,指定--global-style
参数即可禁止变量提升,但这样做相当于回到了当年嵌套依赖的时代,一夜回到解放前,前面提到的嵌套依赖的缺点仍然暴露无遗。
npm/yarn 本身去解决依赖提升的问题貌似很难完成,不过社区针对这个问题也已经有特定的解决方案: dependency-check,地址: https://github.com/dependency-check-team/dependency-check
pnpm 做的更加彻底,独创的一套依赖管理方式不仅解决了依赖提升的安全问题,还大大优化了时间和空间上的性能。
# npm私库的搭建
npm 作为一种包管理工具,无论你是泛前端还是大前端都已经离不开它。它的出现方便了万千少年。让我们跨过了 Ctrl+C、Ctrl+V ,通过 npm install x
的方式将别人的优秀代码模块引入到自己的项目中。这些优秀的模块能被共享的原因,一方面是有 npm 这么一个包管理工具,另外就是 npm 仓库。
对于 npm 仓库,如果你还停留在使用 npm 或者 cnpm 这类官方源的情况下。那么你有必要想想如何搭建一个私有的 npm 仓库。
搭建npm私库的原因:
1.稳定性
网络访问稳定性,私有仓库因为是自己公司在维护,有什么问题能第一时间处理,比如服务宕机…其次资源的稳定性,试想一下,如果哪天你依赖的某个很重要的模块突然被作者删了,那是不是完犊子了
2.私密性
每个公司都有和自己业务强相关的模块,或者对某些开源模块进行个性化的改造,改造后的模块只满足本公司的业务场景,这些模块我们并不希望发布到公共的仓库中去,这时就可以发布到自己的私有仓库在公司内部共享
3.安全性
有了私有仓库后,可以在 npm 模块的质量和安全上做文章,能够有效的防治恶意代码攻击。
搭建
选择cnpmjs.org (opens new window)方案,目前国内像淘宝这样的大厂内部也是选择的它,足以证明它的可靠性和稳定性,拓展性强,配置多样化
环境
- Linux 服务器
- node 环境
- 数据库( Mysql )
- nginx
安装
首先安装cnpmjs.org
git clone https://github.com/cnpm/cnpmjs.org.git
安装项目依赖
npm i
安装完成后找到项目根目录下的配置文件config/index.js
,这里配置文件非常多,刚开始可以只关注下面几项即可,详细配置 (opens new window)戳这里。
服务访问端口
registryPort: 7001, //仓库服务访问端口
webPort: 7002, //web站点访问端口
bindingHost: '', //监听绑定的 Host,默认127.0.0.1,外网访问注释掉此项即可,一般我们不会把我们内部端口暴露出去,可以在nginx层做一个转发,所以这个配置可以注释掉。如果直接外网访问,配置为 0.0.0.0
2
3
数据库配置
database: {
db: 'npm',数据库名称
username: 'admin',//用户
password: 'admin123',//密码
// 数据库类型
// - 目前支持 'mysql', 'sqlite', 'postgres', 'mariadb'
dialect: 'mysql',//默认是sqlite,我选择的mysql
host: '127.0.0.1', //数据库服务地址
port: 3306, // 端口
// 数据库连接池使用默认配置就好
// 目前只支持 mysql 和 postgresql (since v1.5.0)
pool: {
maxConnections: 10,
minConnections: 0,
maxIdleTime: 30000
},
...//其他的暂时不用关注
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
是否启用私有模式
enablePrivate: false,//默认不启用
私有模式下,只有管理员才能发布模块。非管理员发布模块式命名必须以 scopes 字段开头例如:@catfly/packagename
发布前缀
scopes: ['@catfly'],
这个和启用非私有模式配套使用,非私有模式要发布必须配置该项。
管理员配置
admins: {
fengmk2: 'fengmk2@gmail.com',
admin: 'admin@cnpmjs.org',
dead_horse: 'dead_horse@qq.com',
}
2
3
4
5
如果启用私有模式,只有该配置项中的用户可以发布私有包。至于其他的配置项暂时不用关注,后面根据需要在逐渐配置起来。
同步模式
// 同步模式选项
// none: 不进行同步,只管理用户上传的私有模块,公共模块直接从上游获取
// exist: 只同步已经存在于数据库的模块
// all: 定时同步所有源registry的模块
syncModel:'exist'
2
3
4
5
数据库
我选择的 mysql ,请戳这里 (opens new window)。当然你也可以选择其他数据库,目前支持mysql 、 sqlite 、 postgres 、 mariadb ,默认是 sqlite 。
确认数据库启动
service mysql status
登陆数据库
mysql -u root -p test123456
创建数据库
create database npm
查看数据库列表
show database
执行sql文件
cnpmjs.org项目docs目录下已经给我们备好了创建数据库的脚本db.sql.执行
source docs/db.sql
然后使用数据库
use npm
show tables
2
上面两步完成后,就可以将项目跑起来一睹芳容了。因为我们通过 git 克隆的,所以需要进入到项目目录下执行启动服务的命令
npm run start
如果服务器的7002端口访问不了,可能是防火墙的原因,可以关闭防火墙或者开放指定端口
iptables -A INPUT -p tcp --drop -j 7002 DROP
访问 web 页面:xxx.xxx.xxx.xx:7002,就可以看见熟悉的部署在本地的 cnpm 页面了
如果配置域名访问则需要使用nginx代理,这里简单贴一下nginx.conf配置
server{
listen 80;
server_name www.mirrors.catfly.vip;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_pass http://127.0.0.1:7002/; #代理到cnpmjs.org提供的web服务
proxy_set_header X-Real-IP $remote_addr;
}
location /registry/ {
proxy_pass http://127.0.0.1:7001/; # 代理到cnpmjs.org提供的注册服务
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
# error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
验证
在本地安装一个nrm工具,使用比较方便
npm i nrm -g
安装成功后新增我们自己的私有源到nrm源列表中。
nrm add catfly http://www.mirrors.catfly.vip/registry
切换到私有源
nrm use catfly
这个时候本地执行 npm 操作的时候就会去找到我们自己的私有地址
# 进程管理
推荐使用 pm2 进行进程管理,虽然项目本身提供了npm run start
和npm run stop
的能力,但是这对于一个企业级的应用来说还是太弱了,使用 pm2 的好处如下:
- 随时随地多进程管理
- 完善的监控机制,我们可以清晰地看见整个集群的模式、状态,CPU 利用率甚至是内存大小
- 负责均衡
- 进程守护
- ...
全局安装pm2
npm i pm2 -g
启动项目
pm2 start ./dispatch.js
查看服务进程信息
pm2 monit dispatch
# 私有库上云
cnpmjs.org 项目配置项里面有一个 nfs
配置,这里定义了一个 npm 文件系统(NFS)。私有仓库在同步和上传的时候,会交给 NFS 对象相应的函数去处理,NFS 对象返回处理结束之后再返回下载链接,所以通过自定义 NFS 模块可以实现 npm 包的各种定制存储。目前官方默认使用fs-cnpm
,该模块会将上传或者同步的包保存在服务器本地的/root/.cnpmjs.org/doenloads/
目录下。这种方式比较传统,一方面随着私有包数量的不断增加,存储资源会是一个瓶颈。
这个时候将私有包或者同步的资源放到云上就是一个非常好的方案。cnpmjs.org 官方早就为我们想到了这点,给出了下面几种 NFS 模块:
- upyun-cnpm (opens new window):又拍云存储插件
- fs-cnpm (opens new window):本地存储的插件
- sfs-client (opens new window): SFS (opens new window)(Simple FIle Store)存储插件
- qn-cnpm (opens new window):七牛云存储插件
- oss-cnpm (opens new window):阿里云 OSS 存储插件
这些模块已经能够满足我们绝大部分的场景,如果你有特殊的需求,可以参看nfs模块规范 (opens new window)进行定制化开发。这里拿阿里云 oss 存储作为示例。
首先在 cnpmjs.org 项目目录下安装oss-cnpm
模块
cnpm i oss-cnpm
然后在云服务控制台 oss 管理中新增了一个 bucket 来存储 npm 包,也可以通过上传路径区分来复用其他 bucket,毕竟在公司中 bucket 资源一般还是比较紧张的。然后修改项目配置文件,将默认的fs-cnpm
模块替换成oss-cnpm
var oss = require("oss-cnpm");
var nfs = oss.create({
accessKeyId: 'xxxx',
accessKeySecret: 'xxx',
endpoint: 'oss-cn-beijing.aliyuncs.com',
bucket: 'catfly-xxx',
mode: 'private',
})
var config = {
...,
nfs:nfs,
...
}
2
3
4
5
6
7
8
9
10
11
12
13
重启项目,这个时候再发布或者同步资源的时候,服务器本地目录不会有新发布或同步的包了,在 oss 对应的 bucket 里面能找到刚刚发布或者同步的资源。
# Node运行原理
# 运行原理
Node.js 被分为了四层,分别是 应用层
、V8引擎层
、Node API层
和 LIBUV层
。
应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fs
V8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互
NodeAPI层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。
LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心
# 事件循环
node事件循环与浏览器循环是不同的
当Node.js启动时会初始化event loop
, 每一个event loop
都会包含按如下顺序六个循环阶段:
1.timers
阶段: 这个阶段执行 setTimeout(callback)
和 setInterval(callback)
预定的 callback, timer指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定时间过后,timers会尽可能早地执行回调,但系统调度或者其它回调的执行可能会延迟它们。
2.I/O callbacks
阶段: 此阶段执行某些系统操作的回调,例如TCP错误的类型。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED,则某些* nix系统希望等待报告错误。 这将操作将等待在==I/O回调阶段==执行;
3.idle, prepare
阶段: 仅node内部使用;
4.poll
阶段:
获取新的I/O事件, 例如操作读取文件等等,适当的条件下node将阻塞在这里;
如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;
如果 poll 队列为空,则发生以下两件事之一:
如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)。
如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。setImmediate() 实际上是一个特殊的timer,跑在event loop中一个独立的阶段。它使用libuv
的API 来设定在 poll 阶段结束后立即执行回调。
5.check
阶段: 执行 setImmediate()
设定的callbacks,check阶段在poll阶段之后;
6.close callbacks
阶段: 比如 socket.on(‘close’, callback)
的callback会在这个阶段执行;如果一个 socket 或 handle 被突然关掉,close事件将在这个阶段被触发,否则将通过process.nextTick()触发
日常开发的绝大部分异步任务都在timers、poll、check这3个阶段处理的
# Node事件循环与浏览器事件循环的区别
在浏览器环境中,microtask任务队列是每个macrotask执行完之后执行,而在Nodejs中microtask在事件循环的各个阶段之间执行
# setimmediate与settimeout与next tick
两者非常相似,区别在于调用时机不同:
setimmediate设计在poll阶段完成时执行,即check阶段;
setTimeout设计在poll阶段为空闲时,且设定事件达到后执行,但它在timer阶段执行
但当二者在异步i/o callback内部调用时,总是先执行setimmediate,再执行setTimeout
setTimeout(function(){
console.log('timeout')
},0);
setImmediate(function() {
console.log('immediate')
})
//setTimeout可能先执行也可能后执行
const fs = require('fs')
fs.readFile(_filename,()=>{
setTimeout(function(){
console.log('timeout')
},0);
setImmediate(function() {
console.log('immediate')
})
})
//setImmediate总是先于setTimeout
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# process.nextTick
这个函数是独立于Event Loop之外的,有自己的队列,当每个阶段完成时,如果存在nextTick队列就清空队列中的所有回调函数,并且优先于其他microtask执行
# 常用方法
# sleep函数
阻塞主线程,
function sleep(ms) {
return new Promise(resolve => setTimeout(() => resolve(), ms));
}
await sleep(5000);
function sleep(ms) {
var start = Date.now()
expire = start + ms;
while (Date.now() < expire){
return;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 功能模块
# commander.js
前端开发node cli 必备技能。
安装
npm install commander
api
var program = require('commander');
program
.name("intl helper");
.version('0.0.1')
.parse(process.argv);
//执行结果:
node index.js -V
0.0.1
//如果希望程序响应-v选项而不是-V选项,
//只需使用与option方法相同的语法将自定义标志传递给version方法
program
.version('0.0.1', '-v, --version')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
commander.js中命令行有两种可变性,一个叫做option
,意为选项。一个叫做command
,意为命令。
常用api
version
用法: .version('x.y.z')
用于设置命令程序的版本号,
option
用户:.option('-n, --name <name>', 'your name', 'GK')
- 第一个参数是选项定义,分为短定义和长定义。用|,,, 连接。
- 参数可以用
<>
或者[]
修饰,前者意为必须参数,后者意为可选参数。
- 参数可以用
- 第二个参数为选项描述
- 第三个参数为选项参数默认值,可选。
command
用法:.command('init <path>', 'description')
command
的用法稍微复杂,原则上他可以接受三个参数,第一个为命令定义,第二个命令描述,第三个为命令辅助修饰对象。- 第一个参数中可以使用
<>
或者[]
修饰命令参数 - 第二个参数可选。
- 当没有第二个参数时,commander.js将返回
Command
对象,若有第二个参数,将返回原型对象。 - 当带有第二个参数,并且没有显示调用
action(fn)
时,则将会使用子命令模式。 - 所谓子命令模式即,
./pm
,./pm-install
,./pm-search
等。这些子命令跟主命令在不同的文件中。
- 当没有第二个参数时,commander.js将返回
- 第三个参数一般不用,它可以设置是否显示的使用子命令模式。
description
用法:.description('command description')
用于设置命令的描述
用法:.action(fn)
用于设置命令执行的相关回调。fn
可以接受命令的参数为函数形参,顺序与command()
中定义的顺序一致。
parse
用法:program.parse(process.argv)
此api一般是最后调用,用于解析process.argv
。
outputHelp
用法:program.outputHelp()
一般用于未录入参数时自动打印帮助信息。
# inquire
Inquirer.js
可以理解成就是给输入命令行的用户提供一个好看的界面,提供一下功能:
- 有错误反馈;
- 向用户提问;
- 解析输入;
- 校验回答;
- 能在用户输入的时候提供友好的提示。
安装
yarn add inquirer --save-dev
Inquirer 提供prompt
对象,该对象中提供配置项,then
会在用户回答完所有问题后执行,catch
则是报出异常:
prompt是一个对象数组,对象主要包含以下几种配置:
type: 类型,主要类型有input、number、confirm、list、rawlist、expand、checkbox、password、editor;
name:可以理解成当前回答的变量名;
message:问题描述;
default:问题的默认值;
choice:问题选项;
validate:回答的校验器;
filter:回答的过滤器;
transformer:接收用户输入,回答散列和选项标志,并返回一个转换后的值显示给用户。
when:是否应该问这个问题
PageSize:控制选项显示的个数,就是是否当前最多显示多少个选项,如果超过则需要向下才能显示更多;
prefix:更改默认的前缀消息。
suffix:更改默认后缀消息。
askAnswered:如果答案已经存在,就必须提出问题。
loop:是否启用列表循环。
var inquirer = require('inquirer');
inquirer.prompt([
{
type: 'list',
name: 'preset',
message: 'Please pick a preset:',
choices: ['default(babel, eslint)', 'Manually select feature'],
filter: function(val){
return val.toLowerCase();
}
},
{
type: 'input',
name: 'key',
message: "input the text key:",
},
{
type: 'checkbox',
name: 'features',
message: 'Checkout the feature needed for you project:',
choices: [{
name: 'Babel',
}, {
name: 'TypeScript',
},{
name: 'Progressive Web App (PWA) Support',
}, {
name: 'Router',
},{
name: 'Vuex',
}, {
name: 'CSS Pre-processors',
}, {
name: 'Linter / Formatter',
}, {
name: 'Unit Testing',
}, {
name: 'E2E Testing',
}],
pageSize: 9,
validate: function(answer){
if(answer.length < 1){
return 'You must choose at least one topping.';
}
return true;
}
}]).then(answers => {
console.log(JSON.stringify(answers, null, ' '));
}).catch(error => {
console.log(error);
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# chalk
chalk
包的作用是修改控制台中字符串的样式,包括:
- 字体样式(加粗、隐藏等)
- 字体颜色
- 背景颜色
使用
const chalk = require('chalk');
console.log(chalk.red.bold.bgWhite('Hello World'));
2
# process
progress (opens new window)是现在最常用的 npm
包用来渲染进度条。
npm install --save progress
使用
var ProgressBar = require('progress');
var bar = new ProgressBar(':bar', { total: 10 });
var timer = setInterval(function () {
bar.tick();
if (bar.complete) {
console.log('\ncomplete\n');
clearInterval(timer);
}
}, 100);
2
3
4
5
6
7
8
9
10
# http-proxy-middleware包
http-proxy-middleware用于把请求转发到其他服务器的中间件
安装
npm install --save-dev http-proxy-middleware
使用
import express from 'express'
import { createProxyMiddleware } from 'http-proxy-middleware';
app.use(
'/api-metrics/*',
createProxyMiddleware({
target: '192.168.8.8:9090',
pathRewrite: {
'api-metrics': '/api/v1',
},
changeOrigin: true,
})
)
2
3
4
5
6
7
8
9
10
11
12
13
# history fallback包
import history from 'connect-history-api-fallback'
import express from 'express'
const app = express()
app.use(history())
2
3
4
5
6
# prisma
数据库orm
安装
npm install prisma -D
Schema.prisma是prisma主要的配置文件,配置主要分为:
1.DB连接的配置
2.Prisma Client的配置
3.data model的定义
datasource db {
provider = "sqlite"
url = "file:dev.db"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int
email String
name String
}
2
3
4
5
6
7
8
9
10
11
12
13
14
生成数据表
prisma generate
引入
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
2
3
# 文件包
安装
npm install fs-extra
文件包可以替代原生的node fs模块,实现更强大的文件处理功能。
导入
const fs = require('fs-extra')
异步拷贝文件
// Async with promises:
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => console.log('success!'))
.catch(err => console.error(err))
// Sync:
try {
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
2
3
4
5
6
7
8
9
10
11
12
# node-rsa
在node中使用rsa算法
安装
npm install node-rsa
使用
const NodeRSA = require("node-rsa")
const key = new NodeRSA({ b:2048 }) //2048 密钥长度
ket.setOptions({ encryptionSchema: 'pkcs1' }); //指定加密格式,不改格式的话可能会报错
2
3
4
5
6
# youdao-node
使用有道云api进行翻译
# pino
安装
npm install pino
使用
const logger = require('pino')()
logger.info('hello world')
const child = logger.child({ a: 'property' })
child.info('hello child!')
2
3
4
5
6
# 转码包
node默认支持utf8、base64、binary,如果要请求或处理GBK或者Gb2312页面或文件就需要转码
安装iconv-lite
npm install iconv-lite --save
引入
const iconv = require('iconv-lite')
在原来的输出语句中加入解码函数就可以
console.log('stdout'+iconv.decode(data,'GBK'))
# node-redis
# Graphql
安装依赖
npm install apollo-server@2.13.1 graphql@14.6.0 type-graphql@0.17.6
引入
import "reflect-metadata"
import {buildSchema,ObjectType,Field,ID,Resolver,Query} from "type-graphql";
import {ApolloServer} from "apollo-server";
2
3
后端定义schema和resolver
@ObjectType()
class Post{
@Field(type => ID)
id: string;
@Field()
created: Data;
@Field()
content: String;
}
@Resolver(Post)
class PostResolver {
@Query(returns => [Post])
async posts(): Promise<Post[]>{
return [
{
id:"0",
created: new Date(),
content:'aaa'
},
{
id:"1",
created: new Date(),
content:'bbb'
},
{
id:"2",
created: new Date(),
content:'ccc'
},
]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
运行项目,在localhost:4444打开graphql的playground进行测试
# 剪贴板的使用
使用第三方包,安装
npm install clipboard-polyfill
引用
import clipboard from "clipboard-polyfill"
实例
clipboard.writeText("this");
clipboard.readText().then(console.log,console.error);
2
# 终端二维码
qrcode-terminal
安装
npm install -D qrcode-terminal
使用
const qrcode = require('qrcode-terminal')
const url = 'https:www.baidu.com'
qrcode.generate(url,{small:true},(qrcode)=> {
console.log(qrcode)
})
2
3
4
5
6
7
# 判断设备信息
使用navigator对象
export function checkdevice() {
var browser = {
versions: (function() {
var u = navigator.userAgent,
app = navigator.appVersion;
return {
//移动终端浏览器版本信息
trident: u.indexOf("Trident") > -1, //IE内核
presto: u.indexOf("Presto") > -1, //opera内核
webKit: u.indexOf("AppleWebKit") > -1, //苹果、谷歌内核
gecko: u.indexOf("Gecko") > -1 && u.indexOf("KHTML") == -1, //火狐内核
mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
android: u.indexOf("Android") > -1 || u.indexOf("Linux") > -1, //android终端或uc浏览器
iPhone: u.indexOf("iPhone") > -1, //是否为iPhone或者QQHD浏览器
iPad: u.indexOf("iPad") > -1, //是否iPad
webApp: u.indexOf("Safari") == -1, //是否web应该程序,没有头部与底部
};
})(),
language: (navigator.browserLanguage || navigator.language).toLowerCase(),
};
if (browser.versions.mobile) {
//判断是否是移动设备打开。browser代码在下面
// 此时为移动端打开.跳转到移动站
// if(window.location.href.indexOf("ooo0o.com/mobile") != -1){
// return;
// }else {
// window.location.href = "https://www.ooo0o.com/mobile"
// }
var ua = navigator.userAgent.toLowerCase(); //获取判断用的对象
if (ua.match(/MicroMessenger/i) == "micromessenger") {
//在微信中打开
if (browser.versions.ios) {
return "weixinios";
} else {
return "weixin";
}
} else if (browser.versions.android) {
//是否在安卓浏览器打开
// alert('安卓手机中打开的');
/*window.location.href="https://jushizhibo.com/android/app-release.apk";*/
// window.open('https://jushizhibo.com/android/app-release.apk','_self')
return "anzhuo";
} else if (browser.versions.ios) {
//是否在IOS浏览器打开
// alert('IOS中打开的');
/*window.location.href="https://www.baidu.com";*/
// window.open('transparentfactory://xiangqingye','_self')
return "ios";
}
} else {
//此时是非移动端,则跳转PC站
// alert('PC中打开的');
// if(window.location.href.indexOf("ooo0o.com/mobile") != -1){
// window.location.href = "https://www.ooo0o.com"
// }
return "pc";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
使用时导入
import {checkdevice} from 'checkdevice.js'
# 七牛云的使用
安装七牛包
npm install qiniu
新建文件,设置七牛云参数
var bucket='',
var imageUrl='',
var accessKey = '',
var secretKey = '',
var mac = new qiniu.auth.digest.Mac(accessKey,secretKey);
var option={
scope:bucket,
}
var putPolicy= new qiniu.rs.PutPolicy(option)
var uploadToken = putPolicy.uploadToken(mac);
2
3
4
5
6
7
8
9
10
11
上传代码
var config = new qiniu.conf.Config()
config.zone= qiniu.zone.Zone_z0;//选择七牛云的机房
//是否使用https、是否使用cdn加速
config.usehttpsDomain=true;
config.useCdnDomain = true;
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key = '';
formUploader.putFile(uploadToken,key,path.resolve(pathName),putExtra,function(respErr,respBody,respInfo){
if(resqErr){
throw respErr;
}
if(respInfo.statusCode == 200){
console.log(respBody);
}else{
console.log(respInfo.statusCode);
console.log(respBody)
} });
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
https://segmentfault.com/a/1190000017064729
# 发邮件
导入模块Nodemailer
npm install nodemailer
使用方法(包官网https://nodemailer.com/)
//引入包
const nodemailer = require("nodemailer");
//创建邮件请求对象(qq邮箱、163邮箱或其他)
let transporter = nodemailer.createTransport({
host: "smtp.ethereal.email",//邮箱服务器
port: 587,(端口号)
secure: false, // true for 465, false for other ports
auth: {
user: testAccount.user, // 账号
pass: testAccount.pass // 你的邮箱服务器请求密码
}
});
//所发送的邮件信息
let mailobj={
from: '"Fred Foo 👻" <foo@example.com>', // sender address
to: "bar@example.com, baz@example.com", // list of receivers
subject: "Hello ✔", // Subject line
text: "Hello world?", // plain text body
html: "<b>Hello world?</b>" // html body
}
//发送邮件
transporter.sendMail(mailobj);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# MD5加密包
Js-md5
# http爬虫
# node应用打包可执行文件
pkg可以将node项目打包为一个单独的可执行文件,在未安装nodejs的机器上运行。支持win、linux等多系统
npm install pkg --save-dev
# Node应用部署Docker
Docker允许你以应用程序所有的依赖打包成一个标准化的单元,这被称为一个容器,对于应用开发而言,一个容器就是一个蜕化到最基础的linux操作系统,一个镜像是你加载到容器中的软件
在node app应用的目录下新建一个Dockerfile,编辑这个文件
#从Docker站点获取相关镜像
From node:12
#在镜像中创建一个文件夹存放应用程序代码,这将是应用程序工作的目录
WORKDIR /usr/src/app
#安装应用程序的所有依赖
COPY package*.json ./
RUN npm install
#在Docker镜像中使用COPY命令绑定你的应用程序
COPY . .
#定义映射端口,如应用程序的端口为8080,则与docker的镜像做映射
EXPOSE 8080
#最后要定义运行时的CMD命令来运行应用程序,这里使用node serverjs启动服务器
CMD ["node","server.js"]
2
3
4
5
6
7
8
9
10
11
12
13
14
在dockerfile的同一个文件夹下创建.dockerignore文件,带有以下内容
node_modules
npm-debug.log
2
这将避免本地模块和调试日志被拷贝进入你的Docker镜像中,不会把镜像中安装的模块覆盖
准备好之后就可以使用命令行构建和运行镜像
进入dockerfile所在的目录,运行命令构建镜像
docker build -t <username>/node-web-app
构建之后就可以显示或者运行镜像
docker images
使用-d模式以分离模式运行docker容器,使得容器在后台自助运行
开关符-p在容器中把一个公共端口导向到私有的端口
docker run -p 49160:8080 -d <username>/node-web-app
# Node常见问题汇总
# npm ERR! Maximum call stack size exceeded
解决方法:全局更新npm
npm install npm -g
# core-js
warning react-native > create-react-class > fbjs > core-js@1.2.7: core-js@<2.6.8 is no longer maintained. Please, upgrade to core-js@3 or at least to actual version of core-js@2
旧包不在维护,安装新包,自动卸载旧版本
npm install --save core-js@^3
注意:警告可能是由于你所安装的新包在使用旧版本的依赖所导致的警告,但是如果不是你自己开发的,你不能更改包的源码和依赖项,所以这种情况忽略警告吧
# 学习资源
node问答:https://github.com/jimuyouyou/node-interview-questions
https://javascript.ruanyifeng.com/
https://markpop.github.io/2014/10/29/NodeJs%E6%95%99%E7%A8%8B/
node包讲解:https://github.com/chyingp/nodejs-learning-guide