Skip to content
1

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

cosmiconfig ---> 加载配置文件

| 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 ---> 加载配置文件

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

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.
  });

socket.io ---> 双向通信

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

tinypool ---> nodejs workers pool

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

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

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

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

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

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

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

connect ---> 中间件 HTTP 框架

(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);
  }
})();

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

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);

crawlee ---> NodeJs 爬虫

支持 cheerio、playwright、puppeteer

lucia ---> Authentication 库

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 是一个用 TypeScript 编写的 auth 库,它抽象化了处理会话的复杂

winston ---> logger 工具

ponycode ---> 域名编码

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 ---> 域名编码

hono ---> Web 框架

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

nodemailer ---> Send Email with NodeJs

发送邮件服务

发送邮件服务

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

codspeed ---> benchmark 测试

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

例如可以结合 Vitest 做 benchmark

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

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

相似库:jimp-dev/jimp

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

got ---> 友好的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请求库

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

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

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

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

playwright ---> e2e测试框架

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

  • 支持chromium、firefox、webkit
  • 页面截图
  • 模拟手机型号与地理位置
  • 获取浏览器上下文信息
  • 拦截网络请求

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

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

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

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

chokidar ---> 监听文件修改

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

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

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

$ npx vite-node index.ts

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

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

tinybench ---> for benchmark

测试方法的运行时长

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

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

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());

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

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

node-cron ---> cron for NodeJs

Find a file or directory for walking up the parent directories

/
└── Users
    └── sindresorhus
        ├── unicorn.png
        └── foo
            └── bar
                ├── baz
                └── example.js

supertest ---> HTTP 测试框架

autocannon ---> HTTP/1.1 Beachmark

Benchmark for http, supports output tables

import cron from 'node-cron';

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

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

Support async tasks, But must use with gracefulExit

Released under the MIT License.