npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@i.un/libs

v1.0.11

Published

一个实用的ts函数库

Readme

@i.un/libs

一个实用的 TypeScript 函数库,提供跨平台的工具函数和实用程序。

npm version License: ISC

📦 安装

npm install @i.un/libs
# 或
yarn add @i.un/libs
# 或
pnpm add @i.un/libs

🚀 快速开始

import { debounce, singleFlight, createElement, cloneDeep } from '@i.un/libs';

// 防抖函数
const debouncedFn = debounce(() => {
  console.log('防抖执行');
}, 300);

// 单飞:并发调用只请求一次,所有调用方共享结果
const fetchUser = singleFlight((id: string) => fetch(`/api/users/${id}`).then(r => r.json()), {
  key: (id) => id,
});

// 创建DOM元素
const button = createElement({
  tag: 'button',
  children: '点击我',
  attrs: { id: 'myButton' },
  styles: { color: 'red' }
});

// 深度克隆
const cloned = cloneDeep({ name: 'test', items: [1, 2, 3] });

📚 功能模块

🔧 通用工具 (Common)

防抖和延迟

  • debounce(func, wait) - 防抖函数
  • delayExecution(callback, delay, ...args) - 延迟执行函数,返回 Promise

并发控制

  • singleFlight(func, options?) - 单飞:并发调用共享同一次执行
  • singleExecutionWrapper(func, minTime?) - 进行中则丢弃,立即返回 RealUndefined
  • coalesce(func, options?) - 合并尾部:执行期间的触发合并成一次补跑
  • Mutex - 互斥锁,任务串行执行
  • KeyedMutex - 按 key 分组的互斥锁
  • serialize(func, options?) - 把函数包成自动排队的版本

对象操作

  • cloneDeep(obj) - 深度克隆对象
  • isEqual(a, b) - 深度相等比较
  • createDefaultObject(defaultValue) - 创建带默认值的代理对象
  • isCloneable(obj) - 检查对象是否可克隆
  • isPrimitive(value) - 检查值是否为原始类型

缓存

  • cacheWrapper(func, setting) - 为异步函数添加缓存功能
  • generateStableUniqueKey(args) - 生成稳定的唯一键

存储

  • MemoryStore - 内存存储,与 WebStore / ChromeStore 同一套接口

其他

  • getGlobal() - 获取当前环境的全局对象
  • RealUndefined - 表示「真正的未定义」的 Symbol 哨兵值

🌐 浏览器工具 (Browser)

DOM 操作

  • createElement(options) - 创建DOM元素
  • createSvg(params) - 创建SVG元素
  • createSvgFromUrl(url) - 从URL加载SVG

滚动控制

  • scrollToBottom(dom) - 滚动到底部
  • scrollToBottomIfNeeded(dom) - 智能滚动到底部

拖拽

  • Draggable - 让元素可拖拽

存储

  • WebStore - localStorage / sessionStorage 封装

🔌 Chrome 扩展工具 (Chrome)

存储

  • ChromeStore - chrome.storage.local / chrome.storage.sync 封装

消息通信

  • createMessageBus(config?) - 类型安全的消息总线,统一 background / content script / tab 之间的通信
  • isErrorResponse(res) - 判断消息响应是否为错误

Cookie 与扩展信息

  • getCookie(url, name, onlyValue?) - 读取单个 cookie
  • getCookies(url, stringified?) - 读取指定 URL 下的全部 cookie
  • getExtensionInfo() - 获取扩展的 locale、ID、版本

身份验证

  • launchWebAuthFlow(params) - 发起 OAuth 授权流程
  • decodeJWT(token) - 解析 JWT 的 payload

📖 详细文档

并发控制

四种原语按「并发调用发生时怎么办」区分,选错会引入很隐蔽的 bug:

| 原语 | 并发时 | 后来者拿得到结果 | 典型场景 | | --- | --- | --- | --- | | singleFlight | 复用进行中的那次 | 是,同一个 Promise | 同一份数据被多处同时请求 | | singleExecutionWrapper | 丢弃,立即返回 RealUndefined | 否 | 重复执行无意义,且要立刻脱身 | | coalesce | 合并成一次补跑 | 否(Promise<void>) | 触发携带新状态,丢了就丢了 | | Mutex / serialize | 排队串行 | 是 | 读-改-写共享资源 |

四者都只管「同一时刻」的并发,不做结果缓存。跨次调用要不要重复执行是缓存层的职责,请配合 cacheWrapper。

singleFlight —— 共享同一次执行

import { singleFlight } from '@i.un/libs';

const fetchProfile = singleFlight(
  (id: string) => fetch(`/api/profile/${id}`).then(r => r.json()),
  { key: (id) => id },
);

// 同一个 id 并发调用只发一个请求,两个调用方拿到同一份结果
const [a, b] = await Promise.all([fetchProfile('x'), fetchProfile('x')]);

fetchProfile.isRunning('x'); // 对应槽位是否有调用在进行中
fetchProfile.size;           // 进行中的槽位数量

注意:一次失败会传给所有调用方;首次调用卡住时,后来者会跟着挂。高频事件驱动的场景下,需要「立刻脱身」的请用 singleExecutionWrapper。

singleExecutionWrapper —— 进行中则丢弃

import { singleExecutionWrapper, RealUndefined } from '@i.un/libs';

const submit = singleExecutionWrapper(async (form: FormData) => {
  await fetch('/api/submit', { method: 'POST', body: form });
}, 800); // minTime:即使任务提前完成,也至少锁定 800ms,防连点

const result = await submit(form);
if (result === RealUndefined) {
  // 上一次还没结束,本次被丢弃
}

coalesce —— 合并尾部

适用于「触发意味着状态变了,而进行中那轮读到的是旧状态」。丢弃会永久丢掉新状态,共享结果同样丢,只有补跑一次才能收敛到最新状态。

import { coalesce } from '@i.un/libs';

const syncState = coalesce(async () => {
  const latest = await readState();
  await push(latest);
}, {
  onError: (err) => console.error(err), // 返回的 Promise 永不 reject,失败走这里
});

// 首次立即执行;执行期间的多次触发合并成结束后的一次补跑
store.subscribe(() => syncState());

和 debounce 正好相反:debounce 延迟首次响应,coalesce 首次立即执行、把合并放在尾部。两者可以叠加。

Mutex —— 互斥锁

读-改-写共享资源时,读和写各是一次异步调用,中间可以被别的调用插入,后写的会覆盖先写的。把整段放进同一把锁里就不会交错:

import { Mutex } from '@i.un/libs';

const storageLock = new Mutex();

const take = (id: string) => storageLock.run(async () => {
  const map = await readMap();
  const item = map[id];
  delete map[id];
  await writeMap(map);
  return item;
});

const drop = (id: string) => storageLock.run(async () => {
  const map = await readMap();
  delete map[id];
  await writeMap(map);
});

// take 与 drop 并发时也不会交错

多个函数操作同一份资源时必须共用一个 Mutex 实例,各自包一层 serialize 是挡不住交错的。

只需要串行化单个函数时用 serialize;key 是动态无界的(用户 ID、tab ID 等)用 KeyedMutex,它会在队列排空后自动回收 key。

import { serialize, KeyedMutex } from '@i.un/libs';

const writeProfile = serialize(async (id: string, patch: object) => { /* ... */ }, {
  key: (id) => id, // 相同 id 串行,不同 id 并行
});

const locks = new KeyedMutex();
await locks.run(userId, async () => { /* ... */ });

⚠️ 被包装的函数不能通过包装后的入口调用自己:singleFlight 会拿到自己那个尚未完成的 Promise 永远等下去,Mutex 会排在自己后面死锁。需要递归时把真正的逻辑抽成内部函数,只在最外层入口做包装。

防抖函数

import { debounce } from '@i.un/libs';

const debouncedSearch = debounce((query: string) => {
  console.log('搜索:', query);
}, 300);

// 快速连续调用只会执行最后一次
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc'); // 只有这次会执行

存储

MemoryStore / WebStore / ChromeStore 是同一套接口的三种后端,都是按 storageKey 单例、带类型的键值存储:

import { ChromeStore, WebStore, MemoryStore } from '@i.un/libs';

type UserData = { email: string; loginAt: number };

// Chrome 扩展:chrome.storage.local / sync,接口全部异步
const userStore = ChromeStore.local<UserData>('user_data');
await userStore.set('email', '[email protected]');
const email = await userStore.get('email');

// 网页:localStorage / sessionStorage,接口同步
const prefs = WebStore.local<{ theme: 'light' | 'dark' }>('prefs');
prefs.set('theme', 'dark');

// 内存:不落盘,接口同步
const cache = MemoryStore.create<{ token: string }>('cache');

// 三者都支持
userStore.onChanged((changes) => { /* ... */ });
await userStore.setMultiple({ email: '[email protected]', loginAt: Date.now() });
await userStore.remove('email');
await userStore.clear();

消息总线

在 background、content script、popup 之间做类型安全的通信。先定义协议表,send / on 的 payload 和 response 类型会自动推导:

import { createMessageBus } from '@i.un/libs';

type AppMessages = {
  'user/get': { payload: void; response: { id: string; name: string } };
  'tab/highlight': { payload: { selector: string }; response: boolean };
};

const bus = createMessageBus<AppMessages>();

// background 里注册处理器(返回取消函数)
const off = bus.on('user/get', async (_payload, sender) => {
  return { id: '1', name: 'nova' };
});

// content script 里发送到 background
const user = await bus.send('user/get');

// 发送到指定 tab
const ok = await bus.sendToTab(tabId, 'tab/highlight', { selector: '.card' });

// 限定到某个 frame(如只问顶层 frame),字段与 chrome.tabs.sendMessage 的 options 一致
const alive = await bus.sendToTab({ tabId, frameId: 0 }, 'user/get');

Cookie 读取

只能在扩展后台(service worker / background)或有 cookies 权限的扩展页面里调用:

import { getCookie, getCookies } from '@i.un/libs';

const token = await getCookie('https://example.com', 'session', true); // 只要值
const cookie = await getCookie('https://example.com', 'session');      // 完整 Cookie 对象

const header = await getCookies('https://example.com', true); // "a=1; b=2" 形式
const list = await getCookies('https://example.com');         // Cookie 数组

DOM 元素创建

import { createElement } from '@i.un/libs';

// 创建简单元素
const div = createElement({
  tag: 'div',
  children: 'Hello World',
  styles: { color: 'blue', fontSize: '16px' }
});

// 创建复杂嵌套元素
const card = createElement({
  tag: 'div',
  attrs: { class: 'card' },
  children: [
    {
      tag: 'h2',
      children: '标题',
      styles: { margin: 0 }
    },
    {
      tag: 'p',
      children: '内容描述',
      styles: { color: '#666' }
    }
  ]
});

SVG 创建

import { createSvg, createSvgFromUrl } from '@i.un/libs';

// 从字符串创建SVG
const svgString = `
  <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
    <circle cx="50" cy="50" r="40" fill="red" />
  </svg>
`;
const svgElement = createSvg(svgString);

// 从VNode创建SVG
const vnode = {
  type: 'svg',
  props: { width: '100', height: '100' },
  children: [{
    type: 'circle',
    props: { cx: '50', cy: '50', r: '40', fill: 'red' },
    children: null
  }]
};
const svgFromVNode = createSvg(vnode);

// 从URL加载SVG
const svgFromUrl = await createSvgFromUrl('https://example.com/icon.svg');

缓存包装器

import { cacheWrapper } from '@i.un/libs';

// 包装异步函数添加缓存
const fetchUserData = cacheWrapper(async (userId: string) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
}, {
  timeout: 10000,
  timeoutResult: { error: '请求超时' },
  identifier: 'userData'
});

// 第一次调用会执行请求
const user1 = await fetchUserData('123');

// 第二次调用会返回缓存结果
const user2 = await fetchUserData('123'); // 从缓存返回

深度克隆

import { cloneDeep } from '@i.un/libs';

const original = {
  name: 'test',
  items: [1, 2, { nested: true }],
  date: new Date()
};

const cloned = cloneDeep(original);
cloned.items.push(4); // 不会影响原对象

滚动控制

import { scrollToBottom, scrollToBottomIfNeeded } from '@i.un/libs';

const chatContainer = document.getElementById('chat')!;

// 强制滚动到底部
scrollToBottom(chatContainer);

// 智能滚动(只在用户未手动滚动时自动滚动)
scrollToBottomIfNeeded(chatContainer);

🧪 测试

npm test

📝 开发

# 安装依赖
npm install

# 开发模式
npm run dev

# 构建
npm run build

# 生成文档
npm run doc

📄 许可证

ISC

👨‍💻 作者

zhyswan

🔗 相关链接