npm优秀库使用收集整理(长期向)

虽然自栩全沾工程师,但是对于前端圈的了解还是相对缺乏的,尤其是大量的npm包。java的maven/gradle, node的npm, swift的pod,,python的pip,php的Composer,c++的Conan 等等。基本上每一种开发语言都有自己的包管理器。开源三方库汇集了全世界的智慧结晶,有了这些优秀的三方库能够让我们很容易的完成复杂的功能。所以打算开一个长期向的优秀库的使用收集博客。大部分内容是搜索到的优秀博文整理而来。

注: 本篇文章开始于2020年11月20,每次修改或新增时都会将时间更新时最新的时间,每个库的使用也会标注版本和来源。

ajv(v6.12.6)

作用

ajv 是一个非常流行的JSON Schema验证工具,并且拥有非常出众的性能表现。下方的例子中,我们使用ajv来验证用户输入的表单数据是否合法。

 1
 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
const Ajv = require('ajv');

let schema = {
  type: 'object',
  required: ['username', 'email', 'password'],
  properties: {
    username: {
      type: 'string',
      minLength: 4
    },
    email: {
      type: 'string',
      format: 'email'
    },
    password: {
      type: 'string',
      minLength: 6
    },
    age: {
      type: 'integer',
      minimum: 0
    },
    sex: {
      enum: ['boy', 'girl', 'secret'],
      default: 'secret'
    },
  }
};

let ajv = new Ajv();
let validate = ajv.compile(schema);

let valid = validate(data);
if (!valid) console.log(validate.errors);

在上述代码中,我们声明了一个数据模式schema ,这个模式要求目标数据为一个对象,对象可以有五个字段 usernameemailpasswordagesex,并分别定义了五个字段的类型和数据格式要求,并且其中 usernameemailpassword 必填。然后我们使用这个模式去验证用户输入的数据 data 是否满足我们的需求。

注意:

  1. JSON Schema 是一个声明模式描述对象的标准,并非一个库

  2. ajv 是一个JSON Schema标准验证器的实现,除了ajv还有很多其他的库

  3. 代码中的 schema 是使用 JSON Schema 生成的模式描述对象

  4. 代码中 data 是我们要进行检查的数据

参考资料

JSON Schema http://json-schema.org

AJV https://github.com/epoberezkin/ajv

来源: https://segmentfault.com/a/1190000013265287

accounting(0.4.2)

作用

accounting是用来格式化数字的库, 主要提供的方法有 formatMoney() formatColumn() formatNumber() toFixed() unformat()

接下来我们一一介绍:

formatMoney() 格式化货币

1
2
3
4
5
// 默认货币格式:货币符号$,保留两位小数,每千位加逗号
accounting.formatMoney(12345678); // $12,345,678.00

// 指定货币符号、保留小数位、千位间隔符
accounting.formatMoney(12345678, '¥', 2, ''); // ¥12345678.00

formatColumn() 格式化并按列对齐

在制表时,formatColumn() 方法方便我们按照表格列对齐数字和货币符号:

1
accounting.formatColumn([123.5, 3456.615, 777888.99, -5432, -1234567, 0], "$ ");

格式化后的效果:

图片描述

formatNumber() 格式化数字

1
2
accounting.formatNumber(5318008); // 5,318,008
accounting.formatNumber(9876543.21, 3, " "); // 9 876 543.210

toFixed() 保留小数位

和JavaScript内置 Number.prototype.toFixed() 不同的是,accounting.toFixed() 有四舍五入的效果:

1
2
(0.615).toFixed(2); // "0.61"
accounting.toFixed(0.615, 2); // "0.62"

unformat() 解析数字

unformat() 方法能够从任何格式的字符串中解析出原始数字:

1
accounting.unformat("£ 12,345,678.90 GBP"); // 12345678.9

参考资料

http://openexchangerates.github.io/accounting.js/

https://github.com/openexchangerates/accounting.js

来源: https://segmentfault.com/a/1190000013201803

async-retry(1.3.1)

作用

异步的执行对某个操作的重试,可以设置重试次数。

使用demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Packages
const retry = require('async-retry')
const fetch = require('node-fetch')
 
await retry(async bail => {
  // if anything throws, we retry
  const res = await fetch('https://google.com')
 
  if (403 === res.status) {
    // don't retry upon 403
    bail(new Error('Unauthorized'))
    return
  }
 
  const data = await res.text()
  return data.substr(0, 500)
}, {
  retries: 5
})

说明:

  1. 提供的功能可以是async或不是。换句话说,它可以是返回Promise或值的函数。

  2. 提供的函数接收两个参数

1
2
3
Function您可以调用A以中止重试(保释)

Number识别尝试。绝对的首次尝试(在重试之前)为1。
  1. 将opts被传递到node-retry。阅读其文档

  2. retries:重试该操作的最大次数。默认值为10。

  3. factor:要使用的指数因子。默认值为2。

  4. minTimeout:开始第一次重试之前的毫秒数。默认值为1000。

  5. maxTimeout:两次重试之间的最大毫秒数。默认值为Infinity。

  6. randomize:通过乘以介于之间的因数1来随机化超时2。默认值为true。

  7. onRetry:可选Function,在执行新的重试后调用。它传递了Error触发它的参数。

chalk(4.1.0)

将在终端中输出蓝色带下划线的MCC。

chalk

echo -e "\e[34;4mMCC\e[0m"

虽然我们已经学会了,在终端中控制字符颜色的原理和方法,但是这种操作太过于繁琐,每一次都需要查颜色样式手册,然后写出一堆无法阅读的火星文,抓狂!

今天介绍的NPM库chalk就是用来优雅地输出带颜色的文本,不需要记忆、查阅样式手册

安装

npm install chalk

1
const ctx = new chalk.Instance({level: 0});
LevelDescription
0All colors disabled
1Basic color support (16 colors)
2256 color support
3Truecolor support (16 million colors)

使用

chalk 将各种颜色和样式修饰符实现为各个函数,并且支持链式调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 const chalk = require('chalk');          

// 输出蓝色的MCC     

console.log(chalk.blue('MCC'));         

 // 输出蓝色带下划线的MCC     

console.log(chalk.blue.underline('MCC'));    

 // 使用RGB颜色输出  

console.log(chalk.rgb(4,  156, 219).underline('MCC'));     

console.log(chalk.hex('#049CDB').bold('MCC'));     

console.log(chalk.bgHex('#049CDB').bold('MCC'));   

效果

image-20201204181923326

文本样式修饰符函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
reset 重置样式    

bold 加粗    

dim 昏暗    

italic 斜体    

underline 下划线    

inverse 反色    

hidden 隐藏    

strikethrough 删除线    

visible 可见   

颜色函数

 1
 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
black    

red    

green    

yellow    

blue    

magenta    

cyan    

white    

gray ("bright black")    

redBright    

greenBright    

yellowBright    

blueBright    

magentaBright    

cyanBright    

whiteBright

背景色函数

 1
 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
bgBlack    

bgRed    

bgGreen    

bgYellow    

bgBlue    

bgMagenta    

bgCyan    

bgWhite    

bgBlackBright    

bgRedBright    

bgGreenBright    

bgYellowBright    

bgBlueBright    

bgMagentaBright    

bgCyanBright    

bgWhiteBright 

源码

https://github.com/chalk/chalk

ora(5.1.0)

优雅的转圈圈,让你的等待不再煎熬~

ora

yarn add ora

使用

1
2
3
4
5
6
7
8
const ora = require('ora');

const spinner = ora('Loading unicorns').start();

setTimeout(() => {
	spinner.color = 'yellow';
	spinner.text = 'Loading rainbows';
}, 1000);

https://github.com/sindresorhus/ora

figlet(1.5.0)

yarn add figlet

使用

1
2
3
4
5
6
7
8
    figlet('Hello World!!', function(err, data) {
        if (err) {
            console.log('Something went wrong...');
            console.dir(err);
            return;
        }
        console.log(data)
    });

效果

image-20201204182909081

boxen(4.2.0)

给你的代码画上界限,守护自己的地盘~

yarn add boxen

使用

1
2
3
const boxen = require('boxen');

console.log(boxen('unicorn', {padding: 1}));

效果

image-20201204183307440

https://github.com/sindresorhus/boxen

classnames(2.2.6)

快速的组合class name

在前端开发中,我们经常需要JS来判断生成DOM节点CSS类,比如:

1
2
3
4
5
let className='btn-primary';
if(active){
  className+=' active';
}
return <div className={className}>Save</div>; 

在上述代码中,我们需要判断active变量来控制生成的按钮的CSS样式是否是激活状态,在实际开发中,可能会有更多的类似这样的样式控制逻辑,从而干扰阅读业务逻辑代码,使得代码变得很“脏”。 classnames classnames 库对CSS样式类操作进行了封装,方便我们快速使用:

1
2
3
4
5
const classNames = require('classnames');

//...

return <div className={classNames('btn-primary',{ active })}>Save</div>; 

更多调用方式:

1
2
3
4
5
6
classNames('foo', 'bar'); // => 'foo bar' 
classNames('foo', { bar: true }); // => 'foo bar' 
classNames({ 'foo-bar': true }); // => 'foo-bar' 
classNames({ 'foo-bar': false }); // => '' 
classNames({ foo: true }, { bar: true }); // => 'foo bar' 
classNames({ foo: true, bar: true }); // => 'foo bar' 

https://github.com/JedWatson/classnames

concurrently(5.3.0)

主要是方便我们写前端工程化的时候,我们可以同时启动多个命令用。 比如我的前端代码运行起来,既要有一个web工程,同时又要启动一个mock进程。这时候我们就可以使用这个并行解决方案。 文档地址:https://www.npmjs.com/package/concurrently github地址:https://github.com/kimmobrunfeldt/concurrently

全局安装 npm install concurrently -g

项目安装 npm install concurrently -D

以上果是开发阶段写工具用的时候的安装方法。 如果比如node项目在生产环境使用则用以下安装方法: npm install concurrently -s

三、使用方法 3.1 命令行使用方式 语法: concurrently "command1 arg" "command2 arg"

比如我要启动两个node程序:

然后当我们ctrl + c后,他就会把两个进程都停止了。

方式1: 如果我们在package.json里面则要注意引号的问题: "start": "concurrently \"command1 arg\" \"command2 arg\"" 就是将"变成". 方式2:

然后我们要让这个进行并行有两个方式,在命令行执行:

或者:

这块-n之后的两个单词,只能用,间隔,不能加空格。 方式三: 如果我们的package.json里面有以下三个watch类型的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    //...
    "scripts": {
        // ...
        "watch-js": "...",
        "watch-css": "...",
        "watch-node": "...",
        // ...
    },
    // ...
}

那可以批量执行的方式: concurrently "npm:watch-*"

我们可以书写一个node程序来调用concurrently。 我们写一个main.js的代码

1
2
3
4
5
6
7
var concurrently = require('concurrently');

concurrently(['npm:index', "npm:hello"]).then(()=>{
    console.log("success");
}, ()=>{
    console.log("fail")
});

然后它的效果,就是concurrently "npm:index" "npm:hello"

然后当我们按了ctrl + c之后,他会打出success.

上面的index和hello对应的代码是: index:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
var koa = require('koa');
var axios = require('axios')

const app = new koa();

app.use(async (ctx, next)=>{
    ctx.body = (await axios.get("http://127.0.0.1:8001/hello")).data;
})

app.listen(8000)
hello:
var koa = require('koa');

const app = new koa();

app.use((ctx, next)=>{
    ctx.body = 'hello world2';
})

app.listen(8001)

3.2.2 构建失败场景 我们修改上面的hello的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// hello:
var koa = require('koa');

const app = new koa();

app.use((ctx, next)=>{
    ctx.body = 'hello world2';
})

process.exit(-1);

然后我们在运行"node ./main.js" 我们会看到报错,但是程序不会退出。

然后当我们再按ctrl + c后,效果如下:

所以得出一个结论:当我们有一个进程返回失败的话,总体会进入fail的callback中。

应用场景

开发工具 比如当我们跑npm run start的时候,我们同时需要让sass编译,同时webpack也要跑hot 模式,则这个使用可以使用concurrently运行这两个command。 这块我们可以查看grafana里面的前端代码中,关于工具这块代码查看一下就可以看到这块的使用。 grafana地址: https://github.com/grafana/grafana

常见需求

当一个进程起来失败,整体concurrently失败?

我们可以通过killOthers这个参数来解决。 这样当我们有一个程序失败,则直接进入fail callback。也就把所有进程关闭了。 6.2 当一个进程起来失败,但是有可能是跟另外进程有关,这时候需要尝试几次?

这种情况,如上比如npm:index能启动的,npm:hello不能启动,虽然试了三次npm:hello,一直失败,但整体最终不会退出,只有我们按ctrl + c才有用,相当于killOthers就失效了。

cors(2.8.5)

CORS是一个node.js软件包,用于提供可用于通过各种选项启用CORSConnect / Express 中间件。

安装

yarn add cors

开启所有的CORS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
var express = require('express')
var cors = require('cors')
var app = express()
 
app.use(cors())
 
app.get('/products/:id', function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for all origins!'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

开启单路由的CORS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var express = require('express')
var cors = require('cors')
var app = express()
 
app.get('/products/:id', cors(), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a Single Route'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

配置CORS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var express = require('express')
var cors = require('cors')
var app = express()
 
var corsOptions = {
  origin: 'http://example.com',
  optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
 
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for only example.com.'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

配置多个跨域白名单

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var express = require('express')
var cors = require('cors')
var app = express()
 
var whitelist = ['http://example1.com', 'http://example2.com']
var corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  }
}
 
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

如果您不想阻止REST工具或服务器到服务器的请求,!origin请在源函数中添加一个检查,如下所示:

1
2
3
4
5
6
7
8
9
var corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1 || !origin) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  }
}

异步CORS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var express = require('express')
var cors = require('cors')
var app = express()
 
var whitelist = ['http://example1.com', 'http://example2.com']
var corsOptionsDelegate = function (req, callback) {
  var corsOptions;
  if (whitelist.indexOf(req.header('Origin')) !== -1) {
    corsOptions = { origin: true } // reflect (enable) the requested origin in the CORS response
  } else {
    corsOptions = { origin: false } // disable CORS for this request
  }
  callback(null, corsOptions) // callback expects two parameters: error and options
}
 
app.get('/products/:id', cors(corsOptionsDelegate), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for a whitelisted domain.'})
})
 
app.listen(80, function () {
  console.log('CORS-enabled web server listening on port 80')
})

https://github.com/expressjs/cors#readme

cross-env(7.0.3)

NODE_ENV=production像这样设置环境变量时,大多数Windows命令提示符找不到命令,我们使用跨平台环境时能够抹平这个不同。

安装

yarn add corss-env

使用

直接在原来的脚本前加上corss-env

1
2
3
4
5
{
  "scripts": {
    "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
  }
}

cssnao(4.1.10)

什么是缩减(minification)?#

缩减(minification)是指利用各种方法来 减少代码体积的过程。和 gzip 之类的保留 CSS 文件的原始语义(即无损失)的技术不同,缩减(minification) 天生是一个有损失的过程,例如,其中某些值可能会被替换为更简化的 等价语法,或者选择器被合并。

缩减(minification)步骤的最终结果是生成的代码将与 原始代码行为相同,但是某些部分被修改以 尽可能减少代码体积。

将 gzip 压缩和缩减(minification)相结合,可以最大限度的减少 文件体积,但是不要耳听为虚、眼见为实,为什么不去试试 css-size ? css-size 是一个专门对比缩减(minification)前后文件体积大小变化的模块。

cssnano 是什么?#

cssnano 就是这样的一个缩减器,它使基于 Node.js 开发的。cssnano 是一个 PostCSS 插件,可以添加到你的构建流程中,用于确保最终生成的 用于生产环境的 CSS 样式表文件尽可能的小。

如果你不了解什么是构建流程,没关系,我们在 入门指南 中做了讲解。

这对我有什么好处?#

大量的代码优化#

我们提供了众多不同的优化,从简单的 清除空白符到复杂的不同名称的同一 keyframes 的合并等。 更多信息请参考 预设指南

统一的 CSS 处理#

cssnano 基于 PostCSS 来处理 CSS 代码。因为很多 现代化的 CSS 工具都是基于 PostCSS 开发的,因此你可以把这些工具组合起来 并生成一棵单一的抽象语法树(AST)。这就意味着总的处理时间 减少了,因为 CSS 不再需要进行多次解析了。

现代化的架构以及模块化#

因为 cssnano 是基于 PostCSS 的,因此我们可以将 cssnano 的功能拆解为多个 插件,每个插件只需负责执行一项小的优化即可。并且许多优化可以限定 到某一组特定的 CSS 属性上,这就比利用正则表达式对 CSS 做全局处理更加安全。

dotenv(8.2.0)

我们经常需要Node.js程序运行时加载不同的配置,比如开发环境和生产环境的数据数据库配置就可能不一样,使process.env.DB_HOST 环境变量,可以在Node.js程序内部方便获取参数信息。但是,程序启动时,怎样将环境变量传递给程序,这可能会是一个相对麻烦的事情,因为这关系到操作系统层的配置问题。

安装

1
2
3
4
5
# with npm 
npm install dotenv
 
# or with Yarn 
yarn add dotenv

配置文件

在项目根路径下新建.env文件

1
2
3
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3 

然后,在Node.js程序启动时运行:

1
require('dotenv').config() 

接着,我们就可以在接下来的程序中方便地使用环境变量了:

1
2
3
4
5
6
const db = require('db')
db.connect({
  host: process.env.DB_HOST,
  username: process.env.DB_USER,
  password: process.env.DB_PASS
}) 

因此,我们可以创建不同的配置文件并提并git,然后在开发的时候copy一份local.env到根路径重命名为.env就可以做本地开发了,线上部署同理。

image-20201204190948866

https://github.com/motdotla/dotenv#readme

ini(1.3.5)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
; this comment is being ignored
scope = global

[database]
user = dbuser
password = dbpassword
database = use_this_database

[paths.default]
datadir = /var/lib/data
array[] = first value
array[] = second value
array[] = third value

读取ini文件

1
2
3
4
const ini = require('ini');
const fs = require('fs');

let config = ini.parse(fs.readFileSync('config.ini', 'utf-8')); 

写入ini文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var fs = require('fs')
  , ini = require('ini')

var config = ini.parse(fs.readFileSync('./config.ini', 'utf-8'))

config.scope = 'local'
config.database.database = 'use_another_database'
config.paths.default.tmpdir = '/tmp'
delete config.paths.default.datadir
config.paths.default.array.push('fourth value')

fs.writeFileSync('./config_modified.ini', ini.stringify(config, { section: 'section' }))

https://github.com/npm/ini

ip(1.1.5)

ip库能够获取本机IP地址、比较、转换、掩码/子网计算等各种和网络IP相关的操作:

 1
 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
const ip = require('ip');
// 获取本机网卡IP
ip.address();
// 比较两个IP是否相同
ip.isEqual('::1', '::0:1'); // true 
// IP 表示格式互转
ip.toBuffer('127.0.0.1') // Buffer([127, 0, 0, 1]) 
ip.toString(new Buffer([127, 0, 0, 1])) // 127.0.0.1 
ip.toLong('127.0.0.1'); // 2130706433 
ip.fromLong(2130706433); // '127.0.0.1' 
// 判断是否是内网IP
ip.isPrivate('127.0.0.1') // true 
// 判断IP版本
ip.isV4Format('127.0.0.1'); // true 
ip.isV6Format('::ffff:127.0.0.1'); // true 
// 掩码计算
ip.fromPrefixLen(24) // 255.255.255.0 
ip.mask('192.168.1.134', '255.255.255.0') // 192.168.1.0 
ip.cidr('192.168.1.134/26') // 192.168.1.128 
ip.not('255.255.255.0') // 0.0.0.255 
ip.or('192.168.1.134', '0.0.0.255') // 192.168.1.255 
// 子网计算
ip.subnet('192.168.1.134', '255.255.255.192');
// { networkAddress: '192.168.1.128', 
// firstAddress: '192.168.1.129', 
// lastAddress: '192.168.1.190', 
// broadcastAddress: '192.168.1.191', 
// subnetMask: '255.255.255.192', 
// subnetMaskLength: 26, 
// numHosts: 62, 
// length: 64, 
// contains: function(addr){...} } 
// 子网范围判断
ip.cidrSubnet('192.168.1.134/26').contains('192.168.1.190') // true 

参考资料

https://github.com/indutny/node-ip

jschardet(2.2.1)

识别字符编码

yarn add jschardet

jschardet 可以识别出一个Buffer数据所使用的编码格式,具体支持的格式包括:

  • Big5, GB2312/GB18030, EUC-TW, HZ-GB-2312, and ISO-2022-CN (Traditional and Simplified Chinese)
  • EUC-JP, SHIFT_JIS, and ISO-2022-JP (Japanese)
  • EUC-KR and ISO-2022-KR (Korean)
  • KOI8-R, MacCyrillic, IBM855, IBM866, ISO-8859-5, and windows-1251 (Russian)
  • ISO-8859-2 and windows-1250 (Hungarian)
  • ISO-8859-5 and windows-1251 (Bulgarian)
  • windows-1252
  • ISO-8859-7 and windows-1253 (Greek)
  • ISO-8859-8 and windows-1255 (Visual and Logical Hebrew)
  • TIS-620 (Thai)
  • UTF-32 BE, LE, 3412-ordered, or 2143-ordered (with a BOM)
  • UTF-16 BE or LE (with a BOM)
  • UTF-8 (with or without a BOM)
  • ASCII

我们能方便地使用 jschardet 库:

1
2
3
4
5
const jschardet = require("jschardet");
 
 // "次常用國字標準字體表" in Big5
 jschardet.detect("\xa6\xb8\xb1\x60\xa5\xce\xb0\xea\xa6\x72\xbc\xd0\xb7\xc7\xa6\x72\xc5\xe9\xaa\xed");
 // { encoding: "Big5", confidence: 0.99 } 

https://github.com/aadsm/jschardet

iconv-lite(0.6.2)

将转换任意的字符编码到JavaScript内置的Unicode编码,以便于我们的程序和外部系统友好对接。

  • 所有node.js本机编码:utf8,ucs2 / utf16-le,ASCII,二进制,base64,十六进制。
  • 其他Unicode编码:utf16,utf16-be,utf-7,utf-7-imap,utf32,utf32-le和utf32-be。
  • 所有广泛的单字节编码:Windows 125x家族,ISO-8859家族,IBM / DOS代码页,Macintosh家族,KOI8家族,以及iconv库支持的所有其他字符。还支持“ latin1”,“ us-ascii”之类的别名。
  • 所有广泛使用的多字节编码:CP932,CP936,CP949,CP950,GB2312,GBK,GB18030,Big5,Shift_JIS,EUC-JP。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var iconv = require('iconv-lite');
 
// Convert from an encoded buffer to a js string.
str = iconv.decode(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]), 'win1251');
 
// Convert from a js string to an encoded buffer.
buf = iconv.encode("Sample input string", 'win1251');
 
// Check if encoding is supported
iconv.encodingExists("us-ascii")

流API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
http.createServer(function(req, res) {
    var converterStream = iconv.decodeStream('win1251');
    req.pipe(converterStream);
 
    converterStream.on('data', function(str) {
        console.log(str); // Do something with decoded strings, chunk-by-chunk.
    });
});
 
// Convert encoding streaming example
fs.createReadStream('file-in-win1251.txt')
    .pipe(iconv.decodeStream('win1251'))
    .pipe(iconv.encodeStream('ucs2'))
    .pipe(fs.createWriteStream('file-in-ucs2.txt'));
 
// Sugar: all encode/decode streams have .collect(cb) method to accumulate data.
http.createServer(function(req, res) {
    req.pipe(iconv.decodeStream('win1251')).collect(function(err, body) {
        assert(typeof body == 'string');
        console.log(body); // full request body string
    });
});

js-cookie(2.2)

一个简单,轻巧的JavaScript API,用于处理Cookie

yarn add js-cookie

1
2
3
4
5
6
7
8
9
import Cookies from 'js-cookie'

Cookies.set('foo', 'bar') 
Cookies.set('name', 'value', { expires: 7 }) //Create a cookie that expires 7 days from now, valid across the entire site:
Cookies.get('name') // => 'value'
Cookies.get('nothing') // => undefined
Cookies.get() // => { name: 'value' }  //Cookies.get() // => { name: 'value' }
Cookies.get('foo', { domain: 'sub.example.com' }) // `domain` won't have any effect...!
Cookies.remove('name')

demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// cookies.ts 使用示例,给的api己经很精简了,没有再封装的必要,如果想要隔离原则的话也可以封一下
import Cookies from 'js-cookie'

// App
const sidebarStatusKey = 'sidebar_status'
export const getSidebarStatus = () => Cookies.get(sidebarStatusKey)
export const setSidebarStatus = (sidebarStatus: string) => Cookies.set(sidebarStatusKey, sidebarStatus)

// User
const tokenKey = 'pinpinle_token'
export const getToken = () => Cookies.get(tokenKey)
export const setToken = (token: string) => Cookies.set(tokenKey, token)
export const removeToken = () => Cookies.remove(tokenKey)

js-yaml(3.14.1)

js-yaml 是一个专门用来读写YAML格式数据的库,他可以将JS对象转换成YAML字符串,也可以将YAML字符串转换为JS对象。

example.yaml

 1
 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
receipt:     Oz-Ware Purchase Invoice
date:        2012-08-06
customer:  #对象
    given:   Dorothy
    family:  Gale

items:  # 对象数组
    - part_no:   A4786
      descrip:   Water Bucket (Filled)
      price:     1.47
      quantity:  4

    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers
      size:      8
      price:     133.7
      quantity:  1

bill-to:  &id001 # 锚点标记 id001
    street: |  # 多行字符串
            123 Tornado Alley
            Suite 16
    city:   East Centerville
    state:  KS

ship-to:  *id001 # 引用锚点标记id001的数据

specialDelivery:  > # 多行字符串
    Follow the Yellow Brick
    Road to the Emerald City.
    Pay no attention to the
    man behind the curtain.

读取yaml

1
2
3
4
5
6
const yaml = require('js-yaml');
const fs = require('fs');

let obj = yaml.safeLoad(fs.readFileSync('example.yml', 'utf8'));

let str = yaml.safeDump(obj); 

https://github.com/nodeca/js-yaml

log4jsdd(1.0.2)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    const log4js = require('log4js');
    log4js.configure({
        appenders: {
            out: { type: 'stdout'},
            dingding: {
                type: 'log4jsdd',
                hookUrl: '填写获取钉钉里面设置的 webhook 地址',
                title: 'Node 消息'
            }
        },
        categories: { default: { appenders: ['out', 'dingding'], level: 'debug' }}
    });
    
    let app = log4js.getLogger();
    app.info('测试发送到钉钉');

图片

图片

图片

图片

图片

图片

图片

material-ui(4.11.1)

安装material-ui npm install @material-ui/core

安装material icons npm install @material-ui/icons

Material-UI组件是相互独立的,自支持的,工作时仅注入当前组件所需要的样式。这些Material-UI组件并依赖于任何全局的样式表,尽管Material-UI提供了可选的CssBaseline组件。

1
2
3
4
5
6
7
8
9
import React from 'react';
import ReactDOM from 'react-dom';
import Button from '@material-ui/core/Button';  // 导入Button组件
function App() {
  return (
    <Button variant='contained' color='primary'>按钮</Button>
  );
}
ReactDOM.render(<App />, document.querySelector('#app'));

对组件的支持

Materail-UI在努力遵循实际的指导规范时,却并不期望支持每一个组件或者组件的每一个特征。Materail-UI更希望能提供一套能帮助开发者创建引人注目的用户界面所需要的构建模块和经验。

Material-UI 支持哪些平台?

Material-UI支持所有主流的稳定的浏览器,支持IE11以上。Material-UI还支持node.js v6.x以上的服务端渲染。

在哪里可以获取到demo资源?

参见 Material-UI 的GitHub仓库

Material-UI常见的问题列表

  1. 类名冲突时怎么处理?
  2. 当打开Modal时,为什么fixed定位的元素会移动?
  3. 如何禁用掉app中的波浪效果?
  4. 必须使用JSS来控制样式?
  5. 什么时候使用行内样式?什么时候使用类名?
  6. 在Material项目中如何使用React-Router?
  7. 如何合并withStyles() 和 withTheme() ?
  8. 如何获取DOM元素?
  9. 为什么项目中的色彩和官方效果有差异?
  10. Material-UI令人疯狂,我该如何支持这个项目?

与其它UI库进行优势劣势对比

  1. Material-UI
  2. Material Design Lite (MDL)
  3. Material Components Web
  4. Materialize
  5. React Toolbox

**二、**样式

什么是 CssBaseline ?

Material-UI提供了一个CssBaseline组件以建立统一的简单的样式基准。

1
2
3
4
5
6
7
8
import CssBaseline from '@material-ui/core/CssBaseline';
function App() {
  return(
    <CssBaseline>
      {/* 页面元素 */}
    </CssBaseline>
  )
}

有关Material-UI默认样式的详细说明:

  1. 元素的margin为零。
  2. 在标准设备上,使用默认的背景色。在打印时,使用白色作为背景色。
  3. 所有元素的box-sizing都被默认设置为"border-box"。所有元素都包含 ::before 和 ::after 样式,以保证元素的width 和 height 包含padding 和 border。
  4. 使用Roboto字体,以避免了打印时的字体堆叠。
  5. 打印时,不会为标签声明基准字号,但会使用浏览器支持的默认字号16px。

认识Material-UI中的色彩系统

在Material-UI中,色彩系统是非常强大的。它支持粗色调、深阴影和鲜亮的强调色彩。

色彩系统中的重要组成:

  1. Palette,主色调。
  2. Hue / Shade 色调、色度。

Icon系统

Material-UI提供了两个组件用于icon系统。Icon组件用于渲染icons,SvgIcon用于渲染SVG路径。推荐使用SVG Material icons

// 安装@material-ui/icons npm install -S @material-ui/icons

1
2
import React from 'react';
import DeleteIcon from '@material-ui/icons/Delete';

三、布局

Material-UI的布局设计基础

Material-UI布局,使用统一的组件和间距,实现了多平台、多环境和屏幕尺寸的统一性。

1、使用 Grid / Hidden / Breakpoints 这三类组件,实现响应式UI,适配各种尺寸的屏幕。

2、组件使用 z-index属性,实现 Z 轴上的空间层次排布。

Grid 组件

Material-UI的响应式UI,是基于12列的栅格布局。Material-UI的栅格系统是由 Grid 组件实现的,它使用了 CSS 弹性盒模型,它有两种类型的布局,分别是 containers 和 items。item的宽度被设置为百分比,因此它们总是基于父元素而流动、动态地变换大小。items使用内边距padding生成元素之间的间距空间。Material-UI栅格系统支持五种断点模式,分别是 xs / sm / md / lg / xl 。

Hidden 组件

Hidden组件用于隐藏任何内容,它可以结合Grid / Breakpoints组件一起使用。它的实现原理是,基于breakpoints进行显示与隐藏。

Breakpoints 组件

一个 breakpoint 就是一组预定义的屏幕尺寸范围,它决定了特定了布局需求。为了最适宜的用户体验,material design要实现多屏幕适配。Material-UI对material design规范进行了简化实现。每一个breakpoint都会匹配一定范围的屏幕尺寸。

  1. xs, 特别小的屏幕,0px or larger
  2. sm, 小屏幕,600px or larger
  3. md, 中等屏幕,960px or larger
  4. lg, 大屏幕, 1280px or larger
  5. xl, 超大屏幕, 1920px or larger

以上这些取值,在Material-UI中,还支持自定义。Material-UI支持媒体查询,根据屏幕尺寸变化动态地变化元素样式。有时候,仅使用 CSS 是不够的,你或许还需要在屏幕尺寸发生变化时改变DOM内容。这个时候,可以使用 Material-UI 提供的 withWidth() 来实现。

四、工具

Modal 组件

用于创建对话框、lightbox 和 popover。

Popover 组件

用于给元素提供额外的展示/提示信息,类似于html元素的title属性。

Portal 组件

The portal component renders its children into a new “subtree” outside of current component hierarchy.

Transitions 过渡效果

Transition提供了一系列的动画效果。如折叠效果、渐显渐现、Grow效果、Slide效果、Zoom效果等。

Click away listener

监听DOM页面上的所有事件,元素之内、元素之外。比如,在元素之外点击页面时,应该让弹框消失等。

五、Component Demos / Api

如何使用Material-UI组件?

代码演示如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import React from 'react';
import ReactDOM from 'react-dom';
import { withStyles } from '@material-ui/core/styles';
// 导入组件
import AppBar from '@material-ui/core/AppBar';
const App = (props) => {
    return (
        <div>
            { /* 使用组件 */}
            <AppBar position='static' color='default'>title</AppBar>
        </div>
    );
}
ReactDOM.render(<App />, document.getElementById('root'));

在使用 Material-UI 组件时,常需要参考组件的api文档 ,可查看相关属性的使用。

六、定制化

概览

由于组件会被用于各种不同的环境中,Material-UI支持不同类型的定制化需求,从最特殊的到最普通的场景。

  1. 定制一次性样式 在一些特别的场景下,你或许需要改变一个组件的样式。定制方案有:覆盖组件的类名,重写组件样式,使用内联样式等。
  2. 定制动态样式 在一些场景下,你需要使用动态的样式。可选的方案有:使用withStyles,类名切换,CSS变量,行内样式,主题嵌套等。
  3. 为某个组件定制样式
  4. Material Design 样式
  5. 全局的主题样式

主题

主题特指组件的主体颜色、组件表面的墨色、阴影的级别和元素适合的透明度等。主题使得你的应用具有统一的色调。它允许你定制所有的设计方面,以满足你的商业或品牌需求。为了促进应用更高的统一性,Material-UI提供了 light 和 dark 两种主题类型。默认情况下,组件使用 light 主题类型。

什么时候使用 MuiThemeProvider 组件?

如果你想定制主题样式,你需要使用 MuiThemeProvider 组件来包裹那些需要定制样式的组件,用以把主题注入到你的应用中。然而这是可选的,当不使用 MuiThemeProvider 组件时,Material-UI 会使用默认主题。

如何使用主题配置变量?

改变主题配置变量,是匹配Material-UI定制样式最高效的方式。如下几个配置变量是非常重要的:

  1. Palette
  2. Type ( light / dark )
  3. Typography
  4. Other varialbles
  5. Custom variables

查看 Material-UI 的默认主题样式

CSS in JS

https://material-ui.com/zh/getting-started/installation/

nodemon(2.0.6)

nodemon用来监视node.js应用程序中的任何更改并自动重启服务,非常适合用在开发环境中。nodemon将监视启动目录中的文件,如果有任何文件更改,nodemon将自动重新启动node应用程序。

nodemon不需要对代码或开发方式进行任何更改。 nodemon只是简单的包装你的node应用程序,并监控任何已经改变的文件。nodemon只是node的替换包,只是在运行脚本时将其替换命令行上的node。

在项目目录下创建 nodemon.json 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
    "restartable": "rs",
    "ignore": [
        ".git",
        ".svn",
        "node_modules/**/node_modules"
    ],
    "verbose": true,
    "execMap": {
        "js": "node --harmony"
    },
    "watch": [

    ],
    "env": {
        "NODE_ENV": "development"
    },
    "ext": "js json"
}
1
2
3
4
restartable-设置重启模式 
ignore-设置忽略文件 
verbose-设置日志输出模式,true 详细模式 
execMap-设置运行服务的后缀名与对应的命令

修改app.js文件 记得注释最后一行的:module.exports = app;

1
2
3
4
5
6
7
8
9
var debug = require('debug')('my-application'); // debug模块
app.set('port', process.env.PORT || 3000); // 设定监听端口

//启动监听
var server = app.listen(app.get('port'), function() {
  debug('Express server listening on port ' + server.address().port);
});

//module.exports = app;//这是 4.x 默认的配置,分离了 app 模块,将它注释即可,上线时可以重新改回来

在package.json中添加脚本

1
"start": "nodemon app.js"

pkg-dir(5.0.0)

1
2
3
4
5
6
7
8
/
└── Users
    └── xiaomo
        └── foo
            ├── package.json
            └── bar
                ├── baz
                └── example.js

npm install pkg-dir

1
2
3
4
5
6
7
8
9
// example.js
const pkgDir = require('pkg-dir');
 
(async () => {
    const rootDir = await pkgDir(__dirname);
 
    console.log(rootDir);
    //=> '/Users/xiaomo/foo'
})();

返回Promise项目根路径或undefined找不到的根路径。

返回项目的根路径,或者undefined找不到它。

类型:string 默认值:process.cwd()

起始目录。

  • pkg-dir-cli- 此模块的CLI
  • pkg- up-查找最近的package.json文件
  • 查找 -通过上级父目录查找文件

qs(6.9.4)

Node.js 标准库中有一个库叫querystring,这个库用来处理URL查询字符串:

1
2
3
4
const querystring = require('querystring');
 
 querystring.parse('foo=bar&baz=1');
 // { foo:'bar', baz: '1' } 

但是很遗憾,querystring 不支持内嵌对象和数组:

1
2
3
4
const querystring = require('querystring');
 
 querystring.parse('foo[bar]=1&baz[]=2');
 // { 'foo[bar]': '1', 'baz[]': '2' } 

如果我们程序的前端界面form表单中存在数组,标准库的querystring就无法满足我们的需求了。

qs 是querystring的增强版本,最重要的特性就是支持内嵌对象和数组:

1
2
3
4
5
6
7
8
var qs = require('qs');
var assert = require('assert');

var obj = qs.parse('a=c');
assert.deepEqual(obj, { a: 'c' });

var str = qs.stringify(obj);
assert.equal(str, 'a=c');

https://github.com/ljharb/qs

redux-toolkit(1.5.0)

如果你的React项目中使用react hook、redux、redux-thunk,可能你需要用 redux-toolkit (以下简称RTK)优化你的项目结构,它看起来可以这么清爽

Redux 使用常见问题

  • 配置复杂,devtool…
  • 模板代码太多,创建constant,action,reducer…
  • 需要添加很多依赖包,如redux-thunk、immer…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 优化前
 /counter
    constants.ts
    actions.ts
    reducer.ts
    saga.ts
    index.tsxÏ
# 优化后
/counter
    slice.ts
    index.tsx

RTK干了哪些事?

  • configureStore() 包裹createStore,并集成了redux-thunk、Redux DevTools Extension,默认开启
  • createReducer() 创建一个reducer,action type 映射到 case reducer 函数中,不用写switch-case,并集成immer
  • createAction() 创建一个action,传入动作类型字符串,返回动作函数
  • createSlice() 创建一个slice,包含 createReducer、createAction的所有功能
  • createAsyncThunk() 创建一个thunk,接受一个动作类型字符串和一个Promise的函数

新的项目

create-react-app 初始化项目,最受欢迎的脚手架之一

1
2
3
4
# 使用 redux-typescript 模板,推荐使用typescript
npx create-react-app react-rtk-ts --template redux-typescript
# 使用redux 模板
# npx create-react-app react-rtk-ts --template redux

老的项目

  1. 安装 @reduxjs/toolkit
1
2
3
4
# 使用 npm
npm install @reduxjs/toolkit
# 使用 yarn
yarn add @reduxjs/toolkit
  1. configureStore 替换 createStore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React from "react";
import { render } from "react-dom";
- import { createStore } from "redux";
+ import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import App from "./components/App";
import rootReducer from "./reducers";
- const store = createStore(rootReducer);
+ const store = configureStore({
+   reducer: rootReducer,
+});

创建action

1
2
3
4
5
6
7
8
# 创建 action
const increment = createAction('INCREMENT')
const decrement = createAction('DECREMENT')
# 创建reducer
const counter = createReducer(0, {
  [increment]: state => state + 1,
  [decrement]: state => state - 1
})

以上看起比原来结构上好一些,创建action、reducer方便了,但是看着还是不爽,action也可以去掉

创建Slice

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: state => state + 1,
    decrement: state => state - 1
  }
})
# action
counterSlice.action;
# reducer
counterSlice.reducer;

counterSlice 看起起来像

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  name: "counter",
  reducer: (state, action) => newState,
  actions: {
    increment: (payload) => ({type: "counter/increment", payload}),
    increment: (payload) => ({type: "counter/increment", payload})
  },
  caseReducers: {
    increment: (state, action) => newState,
    increment: (state, action) => newState,
  }
}

只需要createSlice,包含着 action,reducer的创建

1
2
3
4
5
6
# 对于一部请求API,可以配合async/await使用
export const incrementAsync = (amount: number): AppThunk => dispatch => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount));
  }, 1000);
};

创建 selecter

1
2
# 配合 react-redux 中 useSelector hook使用
export const selectCount = (state: RootState) => state.counter.value;

完整 slice文件

 1
 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
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppThunk, RootState } from '../../app/store';
interface CounterState {
  value: number;
}
const initialState: CounterState = {
  value: 0,
};
export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: state => {
      state.value += 1;
    },
    decrement: state => {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export const incrementAsync = (amount: number): AppThunk => dispatch => {
  setTimeout(() => {
    dispatch(incrementByAmount(amount));
  }, 1000);
};
export const selectCount = (state: RootState) => state.counter.value;
export default counterSlice.reducer;
署名 - 非商业性使用 - 禁止演绎 4.0