Skip to content
0

NodeJs

NodeJs 相关的包

mono-jsx ---> <html> as a Response

mono-jsx 是一个 JSX 运行时,在 nodeJs,deno,bun,bun,cloudflare worker 等中,在 JS Runtime 中呈现响应对象的<html> 元素。

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "mono-jsx"
  }
}

NodeJs 中的使用示例,利用 srvx 启动:

// app.tsx
import { serve } from "srvx";

serve({
  port: 3000,
  fetch: (req) => (
    <html>
      <h1>Welcome to mono-jsx!</h1>
    </html>
  ),
});

synckit ---> worker_threads 同步执行异步工作

在 vitepress 的一个 commit 中,使用了这个库,取得了不错的性能提升

// runner.js
import { createSyncFn } from 'synckit'

// the worker path must be absolute
const syncFn = createSyncFn(require.resolve('./worker'), {
  tsRunner: 'tsx', // optional, can be `'ts-node' | 'esbuild-register' | 'esbuild-runner' | 'tsx'`
})

// do whatever you want, you will get the result synchronously!
const result = syncFn(...args)
// worker.js
import { runAsWorker } from 'synckit'

runAsWorker(async (...args) => {
  // do expensive work
  return result
})

std-env ---> 检测当前运行的环境

// ESM
import {
  hasTTY,
  hasWindow,
  isDebug,
  isDevelopment,
  isLinux,
  isMacOS,
  isMinimal,
  isProduction,
  isTest,
  isWindows,
  platform,
  isColorSupported,
  nodeVersion,
  nodeMajorVersion
} from "std-env";

verdaccio ---> 私有 npm registry

安装和启动:

$ npm install -g verdaccio
$ verdaccio # start a server at http://localhost:4873

安装包的时候指定本地代理:

$ npm install lodash --registry http://localhost:4873

crossws ---> 跨平台的 WebSocket 库

优雅、类型化且简单的工具包,用于实现跨平台 WebSocket 服务器。

import { defineHooks } from "crossws";
import crossws from "crossws/adapters/<adapter>";

const ws = crossws({
  hooks: {
    open(peer) {
      console.log("[ws] open", peer);
    },

    message(peer, message) {
      console.log("[ws] message", peer, message);
      if (message.text().includes("ping")) {
        peer.send("pong");
      }
    },

    close(peer, event) {
      console.log("[ws] close", peer, event);
    },

    error(peer, error) {
      console.log("[ws] error", peer, error);
    },
  },
});

forge ---> TLS 的 native 实现

可以用于生成证书,例如 vitejs/vite-plugin-basic-ssl 的底层实现就是使用了这个库

// @ts-ignore
import forge from 'node-forge/lib/forge'
// @ts-ignore
import 'node-forge/lib/pki'
const keyPair = forge.pki.rsa.generateKeyPair(keySize)

const cert = forge.pki.createCertificate()

connect ---> 一个 Node.js 的中间件层

算比较老的包

var connect = require('connect');
var http = require('http');

var app = connect();

// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());

// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
    keys: ['secret1', 'secret2']
}));

// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));

// respond to all requests
app.use(function(req, res){
  res.end('Hello from Connect!\n');
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

fdir ---> 目录读取和 glob 模式抓取

类似 fs.readdir 的功能,但是它可以在不到 1 秒的时间内轻松抓取包含 100 万个文件的目录。详细 Benchmark

import { fdir } from "fdir";

// create the builder
const api = new fdir().withFullPaths().crawl("path/to/dir");

// get all files in a directory synchronously
const files = api.sync();

// or asynchronously
api.withPromise().then((files) => {
  // do something with the result here.
});

knip ---> 检测项目中无用的代码

检测没用到的导出,文件,甚至是 NPM 依赖...,目前内置了 .vue 的支持

$ pnpm create @knip/config # 安装
$ pnpm knip # 使用

此外,有一个专注于 ts 代码的移除 tsr,功能更少但更加专注。详情查看:line/tsr

bundlesize ---> 检验打包产物大小

保证产物的大小不超过预期值

$ npm i bundlesize
{
  "scripts": {
    "test": "bundlesize"
  },
  "bundlesize": [
    {
      "path": "./build/vendor.js",
      "maxSize": "3 kB"
    }
  ]
}

tmp ---> 临时文件和目录的创建器

随机创建一个目录或者文件,并自动或者手动在合适的时机清除。更推荐使用 promise 版本的 benjamingr/tmp-promise

import { file } from 'tmp-promise'

(async () => {
  const {fd, path, cleanup} = await file();
  // work with file here in fd
  cleanup();
})();

exegesis ---> 用于实现服务器端 OpenAPI 3.0.0 的工具

简而言之,就是通过读取 OpenAPI 的 yaml 文件信息,直接启动一个 NodeJs 服务器, 此外,也提供了 express 版本的服务

import * as path from 'path';
import * as http from 'http';
import * as exegesis from 'exegesis';

// See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
const options = {
  controllers: path.resolve(__dirname, './src/controllers'),
};

// `compileApi()` can either be used with a callback, or if none is provided,
// will return a Promise.
exegesis.compileApi(
  path.resolve(__dirname, './openapi/openapi.yaml'),
  options,
  (err, middleware) => {
    if (err) {
      console.error('Error creating middleware', err.stack);
      process.exit(1);
    }

    const server = http.createServer((req, res) =>
      middleware(req, res, (err) => {
        if (err) {
          res.writeHead(err.status || 500);
          res.end(`Internal error: ${err.message}`);
        } else {
          res.writeHead(404);
          res.end();
        }
      })
    );

    server.listen(3000);
  }
);
import express from "express";
import path from "path";
import http from "http";
import * as exegesisExpress from "exegesis-express";

async function createServer() {
  // See https://github.com/exegesis-js/exegesis/blob/master/docs/Options.md
  const options = {
    controllers: path.resolve(__dirname, "./controllers"),
  };

  const exegesisMiddleware = await exegesisExpress.middleware(
    path.resolve(__dirname, "./openapi.yaml"),
    options
  );

  const app = express();

  // If you have any body parsers, this should go before them.
  app.use(exegesisMiddleware);

  app.use((req, res) => {
    res.status(404).json({ message: `Not found` });
  });

  app.use((err, req, res, next) => {
    res.status(500).json({ message: `Internal error: ${err.message}` });
  });

  const server = http.createServer(app);
  server.listen(3000);
}

volta ---> 工具版本固定

打开项目时候,自动切换对于的 node 版本和 yarn 版本

$ volta pin node@16

mem ---> 缓存结果

缓存相同的输入,以便更快的输出

import mem from 'mem';
import got from 'got';
import delay from 'delay';

const memGot = mem(got, {maxAge: 1000});

await memGot('https://sindresorhus.com');

// This call is cached
await memGot('https://sindresorhus.com');

await delay(2000);

// This call is not cached as the cache has expired
await memGot('https://sindresorhus.com');

depark ---> 通过PangRank算法计算最重要文件

$ npx deprank ./fixtures

| Filename               | Lines | Dependents | PageRank |
----------------------------------------------------------
| fixtures/core.js       | 3     | 1          | 0.284098 |
| fixtures/utils.js      | 4     | 3          | 0.268437 |
| fixtures/user/user.js  | 4     | 1          | 0.132253 |
| fixtures/todo.js       | 6     | 1          | 0.089796 |
| fixtures/user/index.js | 1     | 1          | 0.089796 |
| fixtures/concepts.js   | 4     | 1          | 0.079694 |
| fixtures/index.js      | 4     | 0          | 0.055926 |

cosmiconfig ---> 加载配置文件

例如当moduleNamemyapp时,会依次加载查找:

  • 带有myapp属性的package.json
  • JSONYAML格式的.myapprc
  • .myapprc.json,.myapprc.yaml,.myapprc.yml,.myapprc.js,.myapprc.cjs
  • myapp.config.js,myapp.config.cjs
const { cosmiconfig, cosmiconfigSync } = require('cosmiconfig');
// ...
const explorer = cosmiconfig(moduleName);

// Search for a configuration by walking up directories.
// See documentation for search, below.
explorer.search()
  .then((result) => {
    // result.config is the parsed configuration object.
    // result.filepath is the path to the config file that was found.
    // result.isEmpty is true if there was nothing to parse in the config file.
  })
  .catch((error) => {
    // Do something constructive.
  });

dotenv ---> 添加环境变量到node进程中

# .env
S3_BUCKET="YOURS3BUCKET"
SECRET_KEY="YOURSECRETKEYGOESHERE"
import 'dotenv/config'
console.log(process.env)

/*
  {
    ...
    S3_BUCKET="YOURS3BUCKET"
    SECRET_KEY="YOURSECRETKEYGOESHERE"
  }
*/

socket.io ---> 双向通信

依赖于 Engine.IO,并不是 websocket。尽管 Socket.IO 确实尽可能使用 WebSocket 作为传输,但它会为每个数据包添加一些元数据:数据包类型、名称空间和需要消息确认时的 ack id。这就是为什么 WebSocket 客户端将无法成功连接到 Socket.IO 服务器,而 Socket.IO 客户端也将无法连接到 WebSocket 服务器

io.on('connection', socket => {
  socket.emit('request', /* … */); // emit an event to the socket
  io.emit('broadcast', /* … */); // emit an event to all connected sockets
  socket.on('reply', () => { /* … */ }); // listen to the event
});

tinypool ---> nodejs workers pool

线程池,也可以试试尤的 yyx990803/okie

import path from 'path'
import Tinypool from 'tinypool'

const pool = new Tinypool({
  filename: new URL('./worker.js', import.meta.url).href,
})

const result = await pool.run({ a: 4, b: 6 })
console.log(result) // Prints 10

birpc ---> 基于消息的双向远程过程调用

基于消息的双向远程过程调用。对 WebSockets 和 Workers 通信很有用

// client 
import type { ServerFunctions } from './types'

const ws = new WebSocket('ws://url')

const clientFunctions: ClientFunctions = {
  hey(name: string) {
    return `Hey ${name} from client`
  }
}

const rpc = createBirpc<ServerFunctions>(
  clientFunctions,
  {
    post: data => ws.send(data),
    on: data => ws.on('message', data),
    // these are required when using WebSocket
    serialize: v => JSON.stringify(v),
    deserialize: v => JSON.parse(v),
  },
)

await rpc.hi('Client') // Hi Client from server


// server
import { WebSocketServer } from 'ws'
import type { ClientFunctions } from './types'

const serverFunctions: ServerFunctions = {
  hi(name: string) {
    return `Hi ${name} from server`
  }
}

const wss = new WebSocketServer()

wss.on('connection', (ws) => {
  const rpc = createBirpc<ClientFunctions>(
    serverFunctions,
    {
      post: data => ws.send(data),
      on: data => ws.on('message', data),
      serialize: v => JSON.stringify(v),
      deserialize: v => JSON.parse(v),
    },
  )

  await rpc.hey('Server') // Hey Server from client
})

compare-versions ---> semver 版本比较器

比较大小,验证条件等功能

import { compareVersions, compare, satisfies, validate } from 'compare-versions';

compareVersions('11.1.1', '10.0.0'); //  1
compare('10.1.8', '10.0.4', '>');  // true
satisfies('10.0.1', '~10.0.0');  // true
validate('1.0.0-rc.1'); // true

write-file-atomic ---> 原子化写文件

(async () => {
  try {
    await writeFileAtomic('message.txt', 'Hello Node', {chown:{uid:100,gid:50}});
    console.log('It\'s saved!');
  } catch (err) {
    console.error(err);
    process.exit(1);
  }
})();

connect ---> 中间件 HTTP 框架

var connect = require('connect');
var http = require('http');
 
var app = connect();
 
// gzip/deflate outgoing responses
var compression = require('compression');
app.use(compression());
 
// store session state in browser cookie
var cookieSession = require('cookie-session');
app.use(cookieSession({
    keys: ['secret1', 'secret2']
}));
 
// parse urlencoded request bodies into req.body
var bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: false}));
 
// respond to all requests
app.use(function(req, res){
  res.end('Hello from Connect!\n');
});
 
//create node.js http server and listen on port
http.createServer(app).listen(3000);

get-node ---> 下载指定版本的 NodeJs

import getNode from 'get-node'

// Download a specific Node.js release
const { path, version } = await getNode('8')
console.log(path) // /home/user/.cache/nve/8.17.0/node
console.log(version) // 8.17.0

crawlee ---> NodeJs 爬虫

支持 cheerio、playwright、puppeteer

import { PlaywrightCrawler, Dataset } from 'crawlee';

// PlaywrightCrawler crawls the web using a headless
// browser controlled by the Playwright library.
const crawler = new PlaywrightCrawler({
    // Use the requestHandler to process each of the crawled pages.
    async requestHandler({ request, page, enqueueLinks, log }) {
        const title = await page.title();
        log.info(`Title of ${request.loadedUrl} is '${title}'`);

        // Save results as JSON to ./storage/datasets/default
        await Dataset.pushData({ title, url: request.loadedUrl });

        // Extract links from the current page
        // and add them to the crawling queue.
        await enqueueLinks();
    },
    // Uncomment this option to see the browser window.
    // headless: false,
});

// Add first URL to the queue and start the crawl.
await crawler.run(['https://crawlee.dev']);

lucia ---> Authentication 库

import { Lucia } from "lucia";

const lucia = new Lucia(new Adapter(db));

const session = await lucia.createSession(userId, {});
await lucia.validateSession(session.id);

Lucia 是一个用 TypeScript 编写的 auth 库,它抽象化了处理会话的复杂

winston ---> logger 工具

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  defaultMeta: { service: 'user-service' },
  transports: [
    // - Write all logs with importance level of `error` or less to `error.log`
    // - Write all logs with importance level of `info` or less to `combined.log`
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});
//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple(),
  }));
}

ponycode ---> 域名编码

参考维基百科

Punycode 的目的是在于国际化域名标签(IDNA)的框架中,使这些(多语言)的域名可以编码为 ASCII。编码语法在文档 RFC 3492 中规定

// encode domain name parts
punycode.encode('mañana'); // 'maana-pta'
punycode.encode('☃-⌘'); // '--dqo34k'

// decode domain name parts
punycode.decode('maana-pta'); // 'mañana'
punycode.decode('--dqo34k'); // '☃-⌘'

hono ---> Web 框架

支持 deno 和 bun,速度快,支持 JSX

import type { FC } from 'hono/jsx'

const app = new Hono()

const Layout: FC = (props) => {
  return (
    <html>
      <body>{props.children}</body>
    </html>
  )
}

const Top: FC<{ messages: string[] }> = (props: { messages: string[] }) => {
  return (
    <Layout>
      <h1>Hello Hono!</h1>
      <ul>
        {props.messages.map((message) => {
          return <li>{message}!!</li>
        })}
      </ul>
    </Layout>
  )
}

app.get('/', (c) => {
  const messages = ['Good Morning', 'Good Evening', 'Good Night']
  return c.html(<Top messages={messages} />)
})

nodemailer ---> Send Email with NodeJs

发送邮件服务

const transporter = nodemailer.createTransport({
  host: 'smtp.163.com',
  port: 465,
  secure: true, // `true` for port 465, `false` for all other ports
  auth: {
    user: 'hi@peterroe.me',
    pass: process.env.EMAIL_AUTH_PASS,
  },
})

await transporter.sendMail({
  from: '"Peter Roe👻" <hi@peterroe.me>', // sender address
  to: ['xxx@xx.com'], // list of receivers
  subject: 'Hello World' + name,
  html: `<h1>Hello World</h1>` // html body
})

下面是我写的一个例子,填入你的邮箱,按下按钮之后,你的邮箱将会收到一封来自我的邮件

除此之外,你可以用这个网站来进行可视化的 HTML 电子邮件设计

codspeed ---> benchmark 测试

例如可以结合 Vitest 做 benchmark

$ pnpm add -D @codspeed/vitest-plugin vitest

示例:

import { defineConfig } from "vitest/config";
import codspeedPlugin from "@codspeed/vitest-plugin";

export default defineConfig({
  plugins: [codspeedPlugin()],
  // ...
});
export function fib(n: number): number {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}
import { bench, describe } from "vitest";
import { fib } from "./fib";

describe("fib", () => {
  bench("fibo 10", () => {
    fib(10);
  });

  bench("fibo 15", () => {
    fib(15);
  });
});
$ vitest bench

[CodSpeed] bench detected but no instrumentation found, falling back to default vitest runner
 RUN  v1.0.3 ...

 fib.bench.ts (2) 1521ms
 fib (2) 1520ms
     name                hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · fibo 10   1,760,687.42  0.0005  1.0279  0.0006  0.0006  0.0007  0.0007  0.0016  ±0.50%   880344   fastest
   · fibo 15     173,932.15  0.0052  0.0594  0.0057  0.0059  0.0064  0.0070  0.0147  ±0.08%    86967


 BENCH  Summary

  fibo 10 - fib.bench.ts > fib
    10.12x faster than fibo 15

sharp ---> 高性能NodeJs图片加工

典型用例是将常见格式的大图像转换为更小的、对 Web 友好的、不同尺寸的 JPEG、PNG、WebP、GIF 和 AVIF 图像

const sharp = require('sharp')

sharp('test.svg')
  .rotate()
  .resize(200)
  .jpeg({ mozjpeg: true })
  .toFile('hh.jpeg')

相似库:jimp-dev/jimp

nock ---> 基于NodeJs的http服务模拟

import got from 'got';
import nock from 'nock';

const scope = nock('https://sindresorhus.com')
	.get('/')
	.reply(500, 'Internal server error')
	.persist();

try {
	await got('https://sindresorhus.com')
} catch (error) {
	console.log(error.response.body);
	//=> 'Internal server error'

	console.log(error.response.retryCount);
	//=> 2
}

scope.persist(false);

got ---> 友好的NodeJs的http请求库

用法类似axios,但是又很多特性:

  • http2支持
  • 代理
  • 重发
  • Cache
  • Unix域Socket
  • 测试
  • Stream
  • ...

qnm ---> 查看依赖的详情信息

$ npm i -g qnm
$ qnm lodash
lodash 4.17.21 2 days ago
├── 4.17.21
├─┬ cli-table2
 └── 3.10.1 1 year ago
└─┬ karma
  └── 3.10.1 1 year ago

shx ---> node的便携式shell命令

无关平台,执行shell命令,只需要加上前缀,例如:

$ npm install -g shx
$ shx rm -rf node_modules
$ shx cp a.txt b.txt

适合在没有shell的环境,如windows上使用

playwright ---> e2e测试框架

一些特性:

  • 支持chromium、firefox、webkit
  • 页面截图
  • 模拟手机型号与地理位置
  • 获取浏览器上下文信息
  • 拦截网络请求
import { test, devices } from '@playwright/test';

test.use({
  ...devices['iPhone 13 Pro'],
  locale: 'en-US',
  geolocation: { longitude: 12.492507, latitude: 41.889938 },
  permissions: ['geolocation'],
})

test('Mobile and geolocation', async ({ page }) => {
  await page.goto('https://maps.google.com');
  await page.locator('text="Your location"').click();
  await page.waitForRequest(/.*preview\/pwa/);
  await page.screenshot({ path: 'colosseum-iphone.png' });
});

happy-dom ---> 更加轻量和快速的DOM环境

相比于JSDOM,更加轻量和快速,常用于测试框架、SSR框架中

import { Window } from 'happy-dom';

const window = new Window();
const document = window.document;

document.body.innerHTML = '<div class="container"></div>';

const container = document.querySelector('.container');
const button = document.createElement('button');

container.appendChild(button);

// Outputs "<div class="container"><button></button></div>"
console.log(document.body.innerHTML);

jsdom ---> 在NodeJs提供DOM环境

属于比较早期的库,很可惜不支持esm

const { JSDOM } = require('jsdom');

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

chokidar ---> 监听文件修改

基于NodeJsfs.watch,但是有着更多的优点

const chokidar = require('chokidar');

// One-liner for current directory
chokidar.watch('.').on('all', (event, path) => {
  console.log(event, path);
});

vite-node ---> 给 node 程序 vite 转换的能力

命令行方式使用,直接执行一个 TS 文件

$ npx vite-node index.ts

或者是 API 方法使用

import { createServer } from 'vite'
import { ViteNodeServer } from 'vite-node/server'
import { ViteNodeRunner } from 'vite-node/client'
import { installSourcemapsSupport } from 'vite-node/source-map'

// create vite server
const server = await createServer({
  optimizeDeps: {
    disabled: true,
  },
})
// this is need to initialize the plugins
await server.pluginContainer.buildStart({})

// create vite-node server
const node = new ViteNodeServer(server)
installSourcemapsSupport({
  getSourceMap: source => node.getSourceMap(source),
})

// create vite-node runner
const runner = new ViteNodeRunner({
  root: server.config.root,
  base: server.config.base,
  fetchModule(id) {
    return node.fetchModule(id)
  },
  resolveId(id, importer) {
    return node.resolveId(id, importer)
  },
})
await runner.executeFile('./example.ts')
await server.close()

local-pkg ---> find message of local package

查找包的信息

import {
  getPackageInfo,
  importModule,
  isPackageExists,
  resolveModule,
} from 'local-pkg'

isPackageExists('local-pkg') // true
isPackageExists('foo') // false

await getPackageInfo('local-pkg')
/* {
 *   name: "local-pkg",
 *   version: "0.1.0",
 *   rootPath: "/path/to/node_modules/local-pkg",
 *   packageJson: {
 *     ...
 *   }
 * }
 */

// similar to `require.resolve` but works also in ESM
resolveModule('local-pkg')
// '/path/to/node_modules/local-pkg/dist/index.cjs'

// similar to `await import()` but works also in CJS
const { importModule } = await importModule('local-pkg')

tinybench ---> for benchmark

测试方法的运行时长

import { Bench } from 'tinybench';

const bench = new Bench({ time: 100 });

bench
  .add('faster task', () => {
    console.log('I am faster')
  })
  .add('slower task', async () => {
    await new Promise(r => setTimeout(r, 1)) // we wait 1ms :)
    console.log('I am slower')
  })
  .todo('unimplemented bench')

await bench.run();

console.table(bench.table());

输出:

(index)Task Nameops/secAverage Time (ns)MarginSamples
0'faster task''41,621'24025.791819761525'±20.50%'4257
1'slower task''828'1207382.7838323202'±7.07%'83

why-is-node-running ---> 检测导致进程没结束的原因

const log = require('why-is-node-running') // should be your first require
const net = require('net')

function createServer () {
  const server = net.createServer()
  setInterval(function () {}, 1000)
  server.listen(0)
}

createServer()
createServer()

setTimeout(function () {
  log() // logs out active handles that are keeping node running
}, 100)

AdminJS ---> admin pane for NodeJs

  • framework: such as express、koa、nestjs

  • database adapter: such as mongoose、sequelize、typeorm

$ npm i adminjs @adminjs/[your framework] @adminjs/[your database adapter]

find-up ---> find a file or directory

Find a file or directory for walking up the parent directories

/
└── Users
    └── sindresorhus
        ├── unicorn.png
        └── foo
            └── bar
                ├── baz
                └── example.js
import path from 'node:path';
import {findUp, pathExists} from 'find-up';

console.log(await findUp('unicorn.png'));
//=> '/Users/sindresorhus/unicorn.png'

expect-type ---> Unit test for TS type

import {foo, bar} from '../foo'
import {expectTypeOf} from 'expect-type'

test('foo types', () => {
  // make sure `foo` has type {a: number}
  expectTypeOf(foo).toMatchTypeOf<{a: number}>()

  // make sure `bar` is a function taking a string:
  expectTypeOf(bar).parameter(0).toBeString()
  expectTypeOf(bar).returns.not.toBeAny()
})

node-cron ---> cron for NodeJs

A simple cron-like job scheduler for Node.js

import cron from 'node-cron';

cron.schedule('* * * * *', () => {
  console.log('running a task every minute');
});

supertest ---> HTTP 测试框架

const app = createApp()

describe('GET /users', function() {
  it('responds with json', function() {
    return request(app)
      .get('/users')
      .set('Accept', 'application/json')
      .expect('Content-Type', /json/)
      .expect(200)
      .then(response => {
          assert(response.body.email, 'foo@bar.com')
      })
  });
});

autocannon ---> HTTP/1.1 Beachmark

Benchmark for http, supports output tables

$ npm i -g autocannon
$ autocannon -c 100 -d 5 -p 10 http://localhost:3000

TIP

Linux 系统上还可以直接使用 ab -n10000 -c100 http://127.0.0.1:3000/ 进行测试,无需安装其他工具

exit-hook ---> run code when process exit

Support async tasks, But must use with gracefulExit

import exitHook from 'exit-hook';

exitHook(signal => {
	console.log(`Exiting with signal: ${signal}`);
});

// You can add multiple hooks, even across files
exitHook(() => {
	console.log('Exiting 2');
});

throw new Error('🦄');

//=> 'Exiting'
//=> 'Exiting 2'

Released under the MIT License.