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

@dwydev/ekit

v0.9.0

Published

Shared utilities, hooks, and helpers for dwydev projects

Readme

@dwydev/ekit

Vue 3 通用工具库:HTTP 请求插件、本地存储、日期格式化、表单校验、常用 Hooks。不包含 UI 组件,可独立用于任意 Vue 3 项目。

安装

npm install @dwydev/ekit

Peer dependencies: vue ^3.4, axios ^1.0


模块

request — Axios 实例工厂

import {
  createRequest,
  tokenPlugin,
  headerPlugin,
  unwrapPlugin,
  refreshTokenPlugin,
} from '@dwydev/ekit'
import type { RequestPlugin, CreateRequestOptions } from '@dwydev/ekit'

createRequest(options?: CreateRequestOptions): AxiosInstance

创建预置插件链的 Axios 实例。

| 选项 | 类型 | 默认值 | 说明 | |------|------|--------|------| | baseURL | string | '/api' | 请求基础路径 | | timeout | number | 30000 | 超时毫秒数 | | plugins | RequestPlugin[] | [] | 拦截器插件列表,按顺序执行 |

const http = createRequest({
  baseURL: '/api',
  timeout: 15000,
  plugins: [tokenPlugin({ getToken: () => localStorage.getItem('token') }), unwrapPlugin()],
})

内置插件

| 函数 | 说明 | |------|------| | tokenPlugin({ getToken }) | 每次请求注入 Authorization: Bearer <token> | | headerPlugin({ name, getValue }) | 每次请求注入自定义 header(值为 null 时跳过) | | unwrapPlugin() | 解包 dwyeapi { code, message, data, timestamp } 响应;code !== "SUCCESS" 时 reject(附带 businessCode / apiResponse) | | refreshTokenPlugin({ isLoginUrl?, getRefreshToken, refreshFn, onRefreshFail }) | 401 时自动刷新 token 并重试原请求,刷新失败调用 onRefreshFail |

RequestPlugin 接口

interface RequestPlugin {
  onRequest?(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>
  onResponse?(response: AxiosResponse): AxiosResponse | Promise<AxiosResponse>
  onResponseError?(error: AxiosError): any
}

storage — 本地存储

import { useStorage, storage } from '@dwydev/ekit'

useStorage<T>(key: string, defaultValue: T): Ref<T>

返回响应式 ref,自动同步到 localStorage(JSON 序列化,deep watch)。值为 null/undefined 时自动删除 key。

const token = useStorage('access_token', '')
token.value = 'abc123' // 自动持久化

storage 对象

非响应式的静态工具:

| 方法 | 签名 | 说明 | |------|------|------| | get | <T>(key, defaultValue?) → T \| undefined | 读取并 JSON.parse,parse 失败返回原始字符串 | | set | (key, value) → void | JSON.stringify 后写入 | | remove | (key) → void | 删除单个 key | | clear | () → void | 清空 localStorage |


date — 日期格式化

import { formatRelativeTime, formatDate, formatDateTime, formatTime } from '@dwydev/ekit'

所有函数接受 string | number | Date

| 函数 | 返回示例 | 说明 | |------|----------|------| | formatRelativeTime(d) | "刚刚" / "5 分钟前" / "3 小时前" / "2 天前" / "2024/1/1" | 距今相对时间(中文),超过 30 天显示本地化日期 | | formatDate(d) | "2024-01-15" | YYYY-MM-DD | | formatDateTime(d) | "2024-01-15 09:30:00" | YYYY-MM-DD HH:mm:ss | | formatTime(d) | "09:30" | HH:mm |


validators — 表单校验

import { isPhone, isEmail, isIdCard, isUrl, isRequired, minLength, maxLength } from '@dwydev/ekit'

| 函数 | 签名 | 说明 | |------|------|------| | isPhone(v) | (string) → boolean | 中国大陆手机号(1[3-9]xxxxxxxx) | | isEmail(v) | (string) → boolean | 邮箱格式 | | isIdCard(v) | (string) → boolean | 18 位居民身份证(末位支持 X/x) | | isUrl(v) | (string) → boolean | 有效 URL(基于 new URL()) | | isRequired(v) | (any) → boolean | 非空:字符串非空白、数组非空、非 null/undefined | | minLength(v, min) | (string, number) → boolean | 字符串长度 >= min | | maxLength(v, max) | (string, number) → boolean | 字符串长度 <= max |


hooks — Vue Composables

import { useDebounce, useClickOutside, useEventListener } from '@dwydev/ekit'

useDebounce<T>(value: Ref<T>, delay?: number): Ref<T>

返回防抖后的 ref,delay 默认 300ms。

const query = ref('')
const debouncedQuery = useDebounce(query, 500)
watch(debouncedQuery, fetchResults)

useClickOutside(target: Ref<HTMLElement | null | undefined>, handler: (e: MouseEvent) => void): void

监听点击目标元素外部的事件,组件卸载时自动清理。

const dropdownRef = ref<HTMLElement | null>(null)
useClickOutside(dropdownRef, () => { isOpen.value = false })

useEventListener(target, event, handler, options?): void

绑定事件监听器,组件挂载时注册,卸载时自动移除。target 可以是 EventTargetRef<EventTarget | null | undefined>

useEventListener(window, 'resize', onResize)
useEventListener(buttonRef, 'keydown', onKey, { passive: true })