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:4873crossws ---> 跨平台的 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@16mem ---> 缓存结果
缓存相同的输入,以便更快的输出
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 ---> 加载配置文件
例如当moduleName是myapp时,会依次加载查找:
- 带有
myapp属性的package.json JSON和YAML格式的.myapprc.myapprc.json,.myapprc.yaml,.myapprc.yml,.myapprc.js,.myapprc.cjsmyapp.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 10birpc ---> 基于消息的双向远程过程调用
基于消息的双向远程过程调用。对 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'); // truewrite-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.0crawlee ---> 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 15sharp ---> 高性能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 agoshx ---> 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 ---> 监听文件修改
基于NodeJs的fs.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 Name | ops/sec | Average Time (ns) | Margin | Samples |
|---|---|---|---|---|---|
| 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、nestjsdatabase 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.jsimport 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:3000TIP
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'