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

zcw-shared

v2.29.1

Published

一个环境无关的 TypeScript 函数库,提供跨平台的工具函数和类型定义。

Downloads

298

Readme

Shared 函数库

一个环境无关的 TypeScript 函数库,提供跨平台的工具函数和类型定义。

技术架构

核心设计原则

环境无关性(Environment Agnostic)

  • 所有函数都通过依赖注入接收环境相关的API
  • 不直接使用 windowdocumentfs 等全局对象
  • 不传入整个模块对象(包括 Node.js API、浏览器 API 和第三方 SDK)
  • 只传入实际使用的函数,使用 Interface['method'] 精确提取类型
  • 支持 Node.js、浏览器、UniApp、微信小程序等多种运行环境

模块化架构

  • 每个函数独立打包,支持按需导入
  • 无统一入口文件,避免不必要的依赖
  • 清晰的类型定义和环境抽象

类型安全优先

  • 完整的 TypeScript 类型定义
  • references/ 提取类型,避免重复定义
  • 使用 import type 导入类型,零运行时开销

目录结构

src/
├── constants/          # 常量定义(颜色模式、软件配置等)
├── functions/          # 核心函数库
│   ├── color/         # 颜色处理函数
│   ├── android/       # Android 构建相关
│   ├── css/           # CSS 处理
│   ├── dom/           # DOM 操作
│   ├── utils/         # 通用工具
│   └── ...
├── schemas/           # Zod Schema 定义(用于运行时验证)
│   ├── auth.schema.ts # 认证相关 Schema
│   └── video.schema.ts # 视频相关 Schema
├── vue-hooks/         # Vue 组合式 API(依赖注入 env)
└── reactive/          # 跨框架响应式辅助(可选)

types/                 # 应用层类型定义
├── auth.d.ts          # 认证相关类型
├── color.d.ts         # 颜色相关类型
├── storage.d.ts       # 存储相关类型
├── video.d.ts         # 视频相关类型
└── ...

references/            # 环境API类型声明
├── node.d.ts          # Node.js API
├── browser.d.ts       # 浏览器 API
├── dom.d.ts           # DOM API
└── ...

docs/                  # VitePress 文档站(含 API 与浏览器端交互演示组件)

架构层次关系

应用层 (functions/ + vue-hooks/ + reactive/)
    ↓ 依赖注入
环境抽象层 (references/)
    ↓ 类型约束
具体环境 (Node.js/Browser/UniApp)

环境无关性实现

1. 依赖注入模式

所有环境相关的操作都通过参数传入,并遵循以下规则:

规则 1:不直接导入第三方库

// ❌ 错误:直接导入 Vue 运行时函数
import { ref, watch } from 'vue'

// ✅ 正确:只能使用 import type 导入 Vue 类型
import type { Ref } from 'vue'

// ❌ 错误:不能从 references 导入 Vue 类型
import type { Ref } from '../../references/vue.d'

// 注意:Vue 类型只能从 'vue' 包直接导入,因为 Vue 的类型系统过于复杂,难以在 references 中完整定义

规则 2:只传入实际使用的函数,不传入整个模块

// ❌ 错误:传入整个模块
import type { FileSystem, Path } from '../../references/node.d'

export function myFunction(fs: FileSystem, path: Path) {
  fs.existsSync(...)
  path.join(...)
}

// ✅ 正确:只传入使用的函数
import type { FileSystem, Path } from '../../references/node.d'

export interface MyFunctionDeps {
  /** 检查文件是否存在 */
  existsSync: FileSystem['existsSync']
  /** 路径拼接 */
  join: Path['join']
}

export function myFunction(deps: MyFunctionDeps) {
  deps.existsSync(...)
  deps.join(...)
}

规则 3:使用 deps. 前缀,不要解构

// ❌ 错误:解构可能导致命名冲突
export function myFunction(deps: MyFunctionDeps) {
  const { existsSync, join } = deps  // 难以追踪来源
  existsSync(...)
}

// ✅ 正确:使用 deps. 前缀
export function myFunction(deps: MyFunctionDeps) {
  deps.existsSync(...)  // 清晰明确
  deps.join(...)
}

规则 4:类型定义在 references 中,使用 typeof 或索引访问提取(Vue 类型除外)

// ❌ 错误:在函数中定义类型
export interface MyFunctionDeps {
  exec: (command: string, callback: (error: any) => void) => any
}

// ✅ 正确:从 references 提取类型
import type { ChildProcess } from '../../references/node.d'
import type { setTimeout } from '../../references/timer.d'

export interface MyFunctionDeps {
  exec: ChildProcess['exec']
  setTimeout: typeof setTimeout
}

Vue 类型特殊规则:

Vue 的类型系统过于复杂,难以在 references/ 中完整定义,因此 Vue 类型只能从 'vue' 包直接导入:

// ✅ 正确:Vue 类型只能从 'vue' 包直接导入
import type { Ref, Component, VNode } from 'vue'

export interface MyFunctionDeps {
  ref: <T>(value: T) => Ref<T>
  component: Component
  node: VNode
}

// ❌ 错误:不能从 references 导入 Vue 类型
import type { Ref } from '../../references/vue.d'  // Vue 没有 references 文件

规则 5:每个函数维护自己完整的依赖,不要分散

// ❌ 错误:依赖分散到多个参数
export function myFunction(
  path: string,
  fsDeps: FsDeps,
  pathDeps: PathDeps,
  systemDeps: SystemDeps
) { }

// ✅ 正确:统一的依赖对象
export interface MyFunctionDeps {
  existsSync: FileSystem['existsSync']
  readFileSync: FileSystem['readFileSync']
  join: Path['join']
  xmlParser: XMLParserConstructor
}

export function myFunction(path: string, deps: MyFunctionDeps) {
  deps.existsSync(...)
  deps.readFileSync(...)
  deps.join(...)
}

规则 6:第三方 SDK 对象也要拆分

不仅是 Node.js 和浏览器 API,第三方 SDK 对象(Vue、微信、UniApp 等)也要遵循同样的规则。

Vue 特殊说明:

  • Vue 类型只能通过 import type {} from 'vue' 导入,不能从 references/ 导入
  • Vue 运行时函数仍然需要通过依赖注入传入,不能直接导入
// ❌ 错误:传入整个 Vue 运行时
import type { VueRuntime } from 'vue'

export interface DynamicMountOptions {
  vue: VueRuntime  // 包含很多未使用的方法
}

export function dynamicMount(options: DynamicMountOptions) {
  options.vue.createVNode(...)
  options.vue.render(...)
}

// ✅ 正确:只传入使用的函数,类型从 'vue' 导入
import type { VueRuntime } from 'vue'

export interface DynamicMountDeps {
  createVNode: VueRuntime['createVNode']
  render: VueRuntime['render']
}

export function dynamicMount(deps: DynamicMountDeps) {
  deps.createVNode(...)
  deps.render(...)
}

常见第三方 SDK 拆分示例:

// 微信小程序 API
import type { Wx } from '../../references/wechat.d'

export interface WxDownloadFileDeps {
  USER_DATA_PATH: Wx['env']['USER_DATA_PATH']
  downloadFile: Wx['downloadFile']
}

// UniApp API
import type { UniApp } from '../../references/uniapp.d'

export interface WaitForPagesDeps {
  getCurrentPages: UniApp['getCurrentPages']
}

// CryptoJS
import type { CryptoJS } from '../../references/crypto-js.d'

export interface GenerateLicenseDeps {
  MD5: CryptoJS['MD5']
}

// 腾讯云 SDK
import type { CloudBaseManager } from '../../references/tencent-cloud.d'

export interface DeployTCBDeps {
  init: CloudBaseManager['init']
}

规则 7:存储能力统一通过 useStorage

  • 不允许直接依赖 localStorage / sessionStorage 等同步 API
  • 必须组合 useStorage 与具体适配器(如 useLocalStorageuseSessionStorageuseStorageWithIndexedDB)创建 storage 对象
  • Env 类型统一定义为 ReturnType<typeof useStorage<string>>(或自定义 value 类型)
  • 在实现中仅使用 storage.get/set/remove/clear 等 Promise API,避免混用同步方法
  • 文档站中的 VitePress 交互示例同样需要使用 useStorage 与适配器,确保行为与生产代码一致
  • 多表结构化 IndexedDB(多 object store、复合索引、transaction)可使用 useDexieShortcuts:将 Dexie 构造器与 indexedDB 注入 deps,应用侧安装 peer 依赖 dexie(版本范围见 package.jsonpeerDependencies)。详见 docs/api/storage/useDexieShortcuts.md

规则 8:浏览器宿主绑定层统一通过 readBrowserHost

  • 库内函数不得直接访问 globalThis / window / document / localStorage
  • 应用边界通过 readBrowserHostFromGlobalThis() 读取宿主,组装为 ReadBrowserHostDeps 后向下传递
  • readBrowser* / createBrowser* / useBrowser* 系列是唯一允许在内部调用 readBrowserHostFromGlobalThis() 的便捷封装,供 Web 宿主直引
  • useBrowser* Hook 可在绑定层直接 import { ref, watch } from 'vue'(与 Vue SFC 同级,属于应用边界)
  • 浏览器存储统一经 createBrowserStringStorageKit / createBrowserSessionStorageKit 组合 useStorage + 适配器;写入只用 storage.set/remove,同步 bootstrap 读可经 backingStorage
  • 新增浏览器绑定逻辑时,优先在 src/functions/browser/readBrowserHost.ts 扩展 BrowserHostSnapshot,不要在业务模块重复读取全局对象
import {
  readBrowserHostDepsFromGlobalThis,
  requireBrowserHostLocalStorage,
  type ReadBrowserHostDeps,
} from '../browser/readBrowserHost'

export function createBrowserExampleAccess(
  hostDeps: ReadBrowserHostDeps = readBrowserHostDepsFromGlobalThis(),
) {
  return {
    read(): string {
      return requireBrowserHostLocalStorage(hostDeps).getItem('key') ?? ''
    },
  }
}

规则 9:编排类函数拆分 {Name}Input{Name}Deps

面向业务流程的编排函数(如 IM 复制、转发、媒体出站)允许将参数拆为两层:

  • {Name}Input:业务数据 + 由调用方实现的 I/O 回调(如 copyImagePixels
  • {Name}Deps:环境能力(documentsetTimeoutURL 等),第二参数传入;Web 宿主可使用 readBrowserHost* 默认注入
export type ExecuteExampleInput = {
  messageId: string
  persist: (id: string) => Promise<void>
}

export interface ExecuteExampleDeps {
  document?: Document
}

export async function executeExample(
  input: ExecuteExampleInput,
  deps: ExecuteExampleDeps = {
    document: readBrowserHostDocument(readBrowserHostDepsFromGlobalThis()),
  },
) {
  // input 承载业务数据与回调,deps 承载环境 API
}

纯数据变换函数可只使用 {Name}Deps 或单一 options;命名与约定见 src/functions/im/imOrchestrationTypes.ts

完整示例

// src/functions/android/buildProject.ts
import type { FileSystem, Path, ChildProcess } from '../../../references/node.d'
import type { setTimeout } from '../../../references/timer.d'

export interface AndroidBuildDependencies {
  /** 检查文件/目录是否存在 */
  existsSync: FileSystem['existsSync']
  /** 读取目录内容 */
  readdirSync: FileSystem['readdirSync']
  /** 获取文件状态 */
  statSync: FileSystem['statSync']
  /** 路径拼接 */
  join: Path['join']
  /** 获取进程平台信息 */
  platform: string
  /** 执行子进程命令 */
  exec: ChildProcess['exec']
  /** 超时设置函数 */
  setTimeout: typeof setTimeout
}

export async function buildProject(
  options: AndroidBuildOptions,
  deps: AndroidBuildDependencies
): Promise<AndroidBuildResult> {
  // 使用 deps. 前缀访问所有依赖
  if (!deps.existsSync(projectPath)) {
    return { success: false, error: '路径不存在' }
  }
  
  const buildPath = deps.join(projectPath, 'build.gradle')
  const files = deps.readdirSync(projectPath)
  
  deps.exec(command, (error, stdout, stderr) => {
    // ...
  })
  
  deps.setTimeout(() => {
    // ...
  }, 1000)
}

2. 类型抽象

references/ 目录定义环境API的抽象类型(接口、类型别名等),所有基础类型都应在此定义:

注意:Vue 类型例外

  • Vue 的类型系统过于复杂,难以在 references/ 中完整定义
  • Vue 类型只能从 'vue' 包直接导入,使用 import type {} from 'vue'
  • Vue 运行时函数仍然需要通过依赖注入传入,不能直接导入
// references/node.d.ts
export interface FileSystem {
  readFileSync(path: string, encoding?: string): string
  writeFileSync(path: string, data: any): void
  existsSync(path: string): boolean
  readdirSync(path: string): string[]
  statSync(path: string): { isDirectory(): boolean; isFile(): boolean }
}

export interface Path {
  join(...paths: string[]): string
  dirname(path: string): string
  basename(path: string): string
}

export interface ChildProcess {
  exec(command: string, callback?: (error: any, stdout: string, stderr: string) => void): any
}

// references/browser.d.ts
export interface Window {
  innerWidth: number
  innerHeight: number
  localStorage: Storage
  Image: new () => HTMLImageElement
  File: new (bits: BlobPart[], name: string, options?: FilePropertyBag) => File
}

// references/dom.d.ts
export interface Document {
  createElement<K extends keyof HTMLElementTagNameMap>(tagName: K): HTMLElementTagNameMap[K]
  head: HTMLElement
}

// references/timer.d.ts
export declare const setTimeout: (callback: (...args: any[]) => void, ms?: number, ...args: any[]) => any
export declare const clearTimeout: (timeoutId: any) => void

// references/console.d.ts
export interface Console {
  log(message?: any, ...optionalParams: any[]): void
  error(message?: any, ...optionalParams: any[]): void
  warn(message?: any, ...optionalParams: any[]): void
}

types/ 目录定义应用层的业务类型:

// types/color.d.ts
export interface RgbColor {
  r: number
  g: number
  b: number
}

// types/android-build.d.ts
export interface AndroidBuildOptions {
  projectPath: string
  buildVariant?: 'debug' | 'release'
}

3. 多环境适配

同一个函数可以在不同环境中使用:

// Node.js 环境
import fs from 'fs'
import path from 'path'
import { buildProject } from 'zcw-shared/functions/android/buildProject'

const result = await buildProject(options, {
  existsSync: fs.existsSync,
  readdirSync: fs.readdirSync,
  statSync: fs.statSync,
  join: path.join,
  platform: process.platform,
  exec: require('child_process').exec,
  setTimeout
})

// 浏览器 Vue 环境
import { ref, shallowRef, watch } from 'vue'
import { useAsyncState } from 'zcw-shared/vue-hooks/state/useAsyncState'

// 传入 Vue 运行时函数和浏览器 API(注意:运行时函数通过依赖注入传入,类型通过 import type 导入)
const { state, isLoading } = useAsyncState(
  async () => fetch('/api/data').then(r => r.json()),
  { 
    immediate: true,
    delay: 1000
  },
  {
    vue: { ref, shallowRef, watch },  // Vue Composition API 运行时函数
    host: { setTimeout }               // 浏览器定时器
  }
)

// 浏览器 DOM 操作
import { getViewportRect } from 'zcw-shared/functions/dom/getViewportRect'

const viewport = getViewportRect({
  innerWidth: window.innerWidth,
  innerHeight: window.innerHeight
})

包管理和导入

PNPM Workspace 架构

项目使用 PNPM Workspace 管理:

// package.json
{
  "name": "shared",
  "exports": {
    "./functions/color/convertColor": "./dist/functions/color/convertColor.js",
    "./types/color": "./types/color.d.ts",
    "./references/node": "./references/node.d.ts"
  }
}

按需导入

每个函数都可以单独导入,避免打包冗余:

// 导入单个函数
import { convertColor } from 'zcw-shared/functions/color/convertColor'
import type { RgbColor } from 'zcw-shared/types/color'

// 导入环境类型
import type { FileSystem } from 'zcw-shared/references/node'

// 导入常量
import { COLOR_VALIDATION_PATTERNS } from 'zcw-shared/constants/colorPatterns'

// 导入 Schema(用于运行时验证)
import { LoginResponseSchema, VideoSchema } from 'zcw-shared/schemas/auth.schema'
import { VideoListResponseSchema } from 'zcw-shared/schemas/video.schema'

// 导入存储抽象(具体路径以 package.json exports 为准)
import useStorage from 'zcw-shared/functions/storage/useStorage'

本地校验以 pnpm exec tsc --noEmitpnpm run buildpnpm run docs:build(文档站)为准;交互演示统一在 docs/ 内维护,说明见 docs/guide/documentation.md(本站内路径为 /guide/documentation)。

开发工作流程

开发注意事项

重要提醒:

  1. 文档同步更新:每次新增或修改函数后,必须同步更新 VitePress 文档。详细规范见下方「文档规范」章节。

  2. 构建验证:改动后在本包根目录执行 pnpm exec tsc --noEmitpnpm run build,确保类型与导出正确。

1. 添加新函数

# 1. 创建函数文件
src/functions/category/newFunction.ts

# 2. 定义类型(如需要)
types/category.d.ts

# 3. 创建函数文档(必须!)
docs/api/category/newFunction.md

# 4. 创建 Playground 组件(如果是浏览器端函数)
docs/.vitepress/components/NewFunctionPlayground.vue

# 5. 更新侧边栏配置
docs/.vitepress/config.ts

# 6. 验证文档编译
npm run docs:dev

# 7. 构建和导出
npm run build

2. 函数开发模板

遵循最新的依赖注入规范:

// src/functions/category/newFunction.ts
import type { FileSystem, Path } from '../../references/node.d'
import type { setTimeout } from '../../references/timer.d'
import type { Console } from '../../references/console.d'

/**
 * 函数依赖接口
 * 注意:
 * 1. 只包含实际使用的函数
 * 2. 使用 Interface['method'] 或 typeof 提取类型
 * 3. 每个依赖都要有清晰的注释
 */
export interface NewFunctionDeps {
  /** 检查文件是否存在 */
  existsSync: FileSystem['existsSync']
  /** 读取文件内容 */
  readFileSync: FileSystem['readFileSync']
  /** 路径拼接 */
  join: Path['join']
  /** 超时函数 */
  setTimeout: typeof setTimeout
  /** 错误日志函数 */
  error: Console['error']
  /** 日志输出函数 */
  log: Console['log']
}

/**
 * 函数描述
 * 
 * @param input - 输入参数
 * @param deps - 环境依赖(包含所有需要的函数)
 * @returns 返回值描述
 * 
 * @example
 * ```typescript
 * import fs from 'fs'
 * import path from 'path'
 * 
 * const result = newFunction(input, {
 *   existsSync: fs.existsSync,
 *   readFileSync: fs.readFileSync,
 *   join: path.join,
 *   setTimeout,
 *   error: console.error,
 *   log: console.log
 * })
 * ```
 */
export function newFunction(
  input: string,
  deps: NewFunctionDeps
): string {
  // ✅ 使用 deps. 前缀访问所有依赖
  if (!deps.existsSync(input)) {
    deps.error('文件不存在')
    return ''
  }
  
  const content = deps.readFileSync(input, 'utf8')
  const outputPath = deps.join(input, '../output.txt')
  
  deps.setTimeout(() => {
    deps.log('处理完成')
  }, 1000)
  
  return content
}

Vue Hooks 开发模板:

// src/vue-hooks/useNewHook.ts
// ✅ Vue 类型只能从 'vue' 直接导入
import type { Ref } from 'vue'
import type { setTimeout } from '../../references/timer.d'
import type { VueCompositionAPI } from '../../types/vue.d'

export interface NewHookEnvironment {
  /** Vue Composition API 运行时函数(通过依赖注入传入) */
  vue: Pick<Required<VueCompositionAPI>, 'ref' | 'watch' | 'onMounted' | 'onUnmounted'>
  host: {
    setTimeout: typeof setTimeout
    clearTimeout: typeof clearTimeout
  }
}

export function useNewHook<T>(
  source: Ref<T>,
  options: NewHookOptions,
  env: NewHookEnvironment
): NewHookReturn<T> {
  const state = env.vue.ref<T>(initialValue)
  
  env.vue.watch(source, () => {
    // 监听逻辑
  })
  
  env.vue.onMounted(() => {
    // 挂载逻辑
  })
  
  env.host.setTimeout(() => {
    // 定时器逻辑
  }, 1000)
  
  return { state }
}

3. 代码检查清单

在提交代码前,请确保:

✅ 依赖注入检查

  • [ ] 没有直接 import 第三方库的运行时函数(如 import { ref } from 'vue'
  • [ ] Vue 类型只能使用 import type {} from 'vue' 导入,不能从 references/ 导入
  • [ ] 没有直接使用全局对象(windowdocumentglobalprocess
  • [ ] 所有环境依赖都通过 depsenv 参数传入
  • [ ] 使用 deps. 前缀访问依赖,没有解构

✅ 类型定义检查

  • [ ] 类型从 references/ 导入,使用 import type(Vue 类型除外)
  • [ ] Vue 类型只能从 'vue' 包直接导入,使用 import type {} from 'vue'
  • [ ] 依赖接口命名为 {FunctionName}Deps{HookName}Environment
  • [ ] 使用 Interface['method']typeof 提取类型
  • [ ] 每个依赖都有清晰的 JSDoc 注释
  • [ ] 没有在函数中重复定义已存在的类型

✅ 函数设计检查

  • [ ] 只传入实际使用的函数,不传入整个模块对象
  • [ ] 第三方 SDK 对象(Vue、Wx、UniApp 等)也要拆分
  • [ ] 函数签名清晰,参数顺序合理
  • [ ] 错误处理完善,不会意外抛出异常
  • [ ] 有完整的 JSDoc 文档注释

✅ 文档检查

  • [ ] 创建了 VitePress 文档
  • [ ] 浏览器函数创建了 Playground 组件
  • [ ] 更新了侧边栏配置
  • [ ] 运行 npm run build 编译成功
  • [ ] 运行 npm run docs:dev 文档正常显示

4. 构建和发布

# 类型检查
npx tsc --noEmit

# 验证文档编译
npm run docs:build

# 构建项目
npm run build

# 发布版本
npm run publish:patch  # 补丁版本
npm run publish:minor  # 次要版本
npm run publish:major  # 主要版本

最佳实践

函数设计

  1. 纯函数优先:无副作用,相同输入产生相同输出
  2. 依赖注入:环境相关API通过 deps 参数传入
  3. 类型安全:完整的TypeScript类型定义
  4. 错误处理:优雅处理异常情况,返回null而非抛出异常
  5. 精确依赖:只传入实际使用的函数,不传入整个模块
  6. 使用前缀:通过 deps. 访问依赖,避免命名冲突

类型定义

  1. 分层设计:业务类型放在 types/,环境类型放在 references/
  2. 类型提取:使用 Interface['method']typeof 从 references 提取类型
  3. 避免重复定义:不在函数中定义已存在于 references 的类型
  4. 完整注释:每个依赖函数都要有清晰的注释说明用途
  5. 统一接口:每个函数有自己完整的 Deps 接口,不依赖通用的 SystemDependencies

依赖管理

  1. 不导入第三方库运行时函数:不直接 import { ref } from 'vue',而是通过 env 对象传入
  2. Vue 类型特殊规则:Vue 类型只能使用 import type {} from 'vue' 导入,不能从 references/ 导入
  3. 不依赖全局对象:不直接使用 windowdocumentglobal;浏览器宿主统一经 readBrowserHost(规则 8)
  4. 接口命名规范{FunctionName}Deps 或编排函数的 {FunctionName}Input + {FunctionName}Deps
  5. 使用 deps. 前缀:所有依赖访问都使用 deps.methodName(),不解构

快速检查清单

在编写新函数或修改现有函数时,快速检查以下要点:

✅ 9 大核心规则:

  1. ❌ 不直接导入第三方库运行时函数 → ✅ 从 references/ 导入类型(Vue 类型除外)
  2. ❌ Vue 类型不能从 references/ 导入 → ✅ 只能使用 import type {} from 'vue'
  3. ❌ 不传入整个模块对象 → ✅ 只传入实际使用的函数
  4. ❌ 不解构 deps 对象 → ✅ 使用 deps. 前缀访问
  5. ❌ 不重复定义类型 → ✅ 使用 Interface['method'] 提取
  6. ❌ 不分散依赖到多个参数 → ✅ 统一的 Deps 接口
  7. ❌ 第三方 SDK 也不能整个传入 → ✅ Vue、Wx 等也要拆分
  8. ❌ 业务代码不直接读 globalThis → ✅ 经 readBrowserHost / readBrowser* 绑定层注入
  9. ❌ 编排函数混用环境与业务参数 → ✅ {Name}Input + {Name}Deps 分层(见规则 9)

✅ 命名规范:

  • 函数依赖接口:{FunctionName}Deps
  • 编排函数业务入参:{FunctionName}Input
  • Hook 环境接口:{HookName}Environment
  • 浏览器绑定便捷函数:readBrowser* / createBrowser* / useBrowser*
  • Playground 组件:{FunctionName}Playground.vue

✅ 文档完整性:

  • [ ] Markdown 文档(docs/api/category/functionName.md
  • [ ] Playground 组件(浏览器函数必须)
  • [ ] 侧边栏配置已更新

常见错误和解决方案

❌ 错误 1:直接导入第三方库运行时函数

import { ref, watch } from 'vue'  // ❌ 错误:导入了运行时函数

解决方案:

// ✅ 正确:Vue 类型只能从 'vue' 直接导入
import type { Ref } from 'vue'
import type { VueCompositionAPI } from '../../types/vue.d'

export interface UseMyHookEnvironment {
  vue: Pick<Required<VueCompositionAPI>, 'ref' | 'watch'>
}

export function useMyHook(env: UseMyHookEnvironment) {
  const state = env.vue.ref(0)  // 运行时函数通过依赖注入传入
  env.vue.watch(state, () => {})
}

// 使用时:
// import { ref, watch } from 'vue'
// const result = useMyHook({ vue: { ref, watch } })

❌ 错误 2:传入整个模块对象

export function myFunction(fs: FileSystem, path: Path) {  // ❌ 错误
  fs.existsSync(...)
}

解决方案:

export interface MyFunctionDeps {
  existsSync: FileSystem['existsSync']  // ✅ 正确
}

export function myFunction(deps: MyFunctionDeps) {
  deps.existsSync(...)
}

❌ 错误 3:解构依赖对象

export function myFunction(deps: MyFunctionDeps) {
  const { existsSync, join } = deps  // ❌ 错误,可能命名冲突
}

解决方案:

export function myFunction(deps: MyFunctionDeps) {
  deps.existsSync(...)  // ✅ 正确,清晰明确
  deps.join(...)
}

❌ 错误 4:在函数中定义类型

export interface MyFunctionDeps {
  exec: (cmd: string, cb: (err: any) => void) => any  // ❌ 错误
}

解决方案:

import type { ChildProcess } from '../../references/node.d'

export interface MyFunctionDeps {
  exec: ChildProcess['exec']  // ✅ 正确,从 references 提取
}

❌ 错误 5:依赖全局对象

export function myFunction(window: Window) {  // ❌ 错误
  const width = window.innerWidth
  window.localStorage.setItem(...)
}

解决方案:

import type { Window } from '../../references/browser.d'

export interface MyFunctionDeps {
  innerWidth: Window['innerWidth']  // ✅ 正确
  setItem: (key: string, value: string) => void
}

export function myFunction(deps: MyFunctionDeps) {
  const width = deps.innerWidth
  deps.setItem('key', 'value')
}

❌ 错误 6:传入整个第三方 SDK 对象

// ❌ 错误:Vue 类型可以从 'vue' 导入,但不能传入整个运行时对象
import type { VueRuntime } from 'vue'
import type { Wx } from '../../references/wechat.d'

export interface MyFunctionDeps {
  vue: VueRuntime  // ❌ 错误,包含很多未使用的方法
  wx: Wx           // ❌ 错误,包含很多未使用的方法
}

export function myFunction(deps: MyFunctionDeps) {
  deps.vue.createVNode(...)
  deps.wx.downloadFile(...)
}

解决方案:

// ✅ 正确:Vue 类型从 'vue' 导入,运行时函数通过依赖注入传入
import type { VueRuntime } from 'vue'
import type { Wx } from '../../references/wechat.d'

export interface MyFunctionDeps {
  createVNode: VueRuntime['createVNode']        // ✅ 正确:只传入使用的函数
  downloadFile: Wx['downloadFile']              // ✅ 正确
  USER_DATA_PATH: Wx['env']['USER_DATA_PATH']  // ✅ 嵌套属性也要精确提取
}

export function myFunction(deps: MyFunctionDeps) {
  deps.createVNode(...)
  deps.downloadFile(...)
  const path = `${deps.USER_DATA_PATH}/file.txt`
}

常见第三方 SDK 对象拆分:

  • VueRuntime → 只传入 createVNode, render 等实际使用的方法(类型用 import type {} from 'vue' 导入)
  • Wx (微信) → 只传入 downloadFile, request 等实际使用的方法(类型从 references/wechat.d 导入)
  • UniApp → 只传入 getCurrentPages 等实际使用的方法(类型从 references/uniapp.d 导入)
  • CryptoJS → 只传入 MD5, SHA256 等实际使用的方法(类型从 references/crypto-js.d 导入)
  • CloudBaseManager → 只传入 init 等实际使用的方法(类型从 references/tencent-cloud.d 导入)
  • Sharp (图像处理) → 已经是函数,直接作为依赖(类型从 references/sharp.d 导入)

文档规范

重要:每个函数或 Hook 都必须有完整的文档!

0. 文档网站开发

本项目使用 VitePress 构建文档网站。

开发命令:

# 本地开发文档网站
npm run docs:dev

# 构建生产版本
npm run docs:build

# 预览构建后的网站
npm run docs:preview

文档目录结构:

docs/
├── index.md                      # 首页
├── guide/                        # 指南文档
├── api/                          # API文档
│   ├── color/                   # 按功能分类
│   ├── string/
│   └── utils/
└── .vitepress/                   # VitePress配置
    ├── config.ts                # 网站配置和侧边栏
    ├── theme/                   # 主题配置
    └── components/              # Playground组件

1. 文档文件结构

每个函数必须有独立的 Markdown 文档:

docs/api/
├── color/
│   ├── convertColor.md          # 每个函数一个文档
│   ├── colorValidation.md
│   └── ...
├── string/
│   ├── capitalize.md
│   └── ...
└── utils/
    ├── debounce.md
    └── ...

2. 文档内容模板

每个函数文档应按照以下结构编写(按优先级排序):

必需部分
# functionName

<!-- 1. 函数描述(一句话) -->
简短描述函数的功能和用途。

<!-- 2. Playground 组件(浏览器端函数必须有) -->
<script setup>
import FunctionNamePlayground from '../../.vitepress/components/FunctionNamePlayground.vue'
</script>

<FunctionNamePlayground />

<!-- 3. 前置依赖(如果函数有 deps 参数) -->
## 前置依赖

### 依赖参数

| 参数名 | 类型 | 说明 |
|--------|------|------|
| `deps.xxx` | `Type` | 依赖说明 |

### 环境要求

- **第三方库名**: 用途说明

\`\`\`bash
npm install library-name
\`\`\`

<!-- 4. 函数签名 -->
## 函数签名

\`\`\`typescript
function functionName(
  param1: Type1,
  param2: Type2,
  deps?: DepsType
): ReturnType

interface FunctionNameDeps {
  // 依赖接口定义
}
\`\`\`

<!-- 5. 参数表格 -->
## 参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `param1` | `Type1` | 是 | 参数说明 |
| `param2` | `Type2` | 否 | 可选参数说明 |

<!-- 6. 返回值 -->
## 返回值

| 类型 | 说明 |
|------|------|
| `ReturnType` | 返回值说明 |

<!-- 7. 工作原理 -->
## 工作原理

1. 步骤1说明
2. 步骤2说明
3. 步骤3说明
4. 返回结果

补充说明...
可选部分
<!-- 异常(如果函数会抛出错误) -->
## 异常

| 错误类型 | 触发条件 | 错误信息 |
|----------|----------|----------|
| `Error` | 条件 | 错误文本 |
文档编写注意事项
  • 必须包含:函数描述、函数签名、参数表格、返回值、工作原理
  • 浏览器端函数必须有 Playground 组件
  • 有 deps 参数的函数必须有前置依赖说明
  • 不要包含## 导入 章节(已在首页说明)
  • 不要包含:冗长的使用示例代码
  • 不要包含## 注意事项 章节(信息整合到其他部分)
  • 使用表格:参数、返回值、异常等都用表格展示
  • 工作原理简明扼要:说明核心逻辑即可,不要过于详细

3. Playground 组件规范

浏览器端函数必须创建交互式 Playground 组件:

命名规范:

  • 文件名:{FunctionName}Playground.vue(首字母大小写,驼峰命名)
  • 位置:docs/.vitepress/components/

设计规范:

所有 Playground 组件必须遵循统一的设计语言,确保视觉一致性和用户体验:

视觉风格
  1. 容器背景

    .playground-container {
      width: 100%;                    /* 重要:占满容器宽度 */
      padding: 24px;
      background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
      border-radius: 16px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
      box-sizing: border-box;        /* 重要:确保 padding 不溢出 */
    }
  2. 卡片样式

    .card {
      background: white;
      border-radius: 12px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);  /* 注意:是 12px 和 0.06,不是 8px 和 0.08 */
    }
  3. 标题样式

    h4 {
      margin: 0 0 16px 0;
      font-size: 15px;
      font-weight: 600;
      color: #374151;                /* 重要:统一使用 #374151,不是 #2c3e50 */
      text-transform: uppercase;     /* 重要:标题全部大写 */
      letter-spacing: 0.05em;        /* 重要:增加字母间距 */
    }
配色方案
  1. 主色调

    • 主要按钮:#3b82f6(蓝色)
    • 成功状态:#10b981(绿色)
    • 错误状态:#ef4444(红色)
    • 警告状态:#f59e0b(橙色)
    • 中性按钮:#6b7280(灰色)
  2. 文本颜色

    • 主标题:#374151(15-16px)
    • 正文:#6b7280(14px)
    • 次要文本:#9ca3af(13px)
  3. 边框和背景

    • 边框:#e5e7eb(2px solid)
    • 卡片背景:#ffffff
    • 输入框背景:#f9fafb
交互元素
  1. 输入框

    input, textarea, select {
      padding: 12px 14px;
      border: 2px solid #e5e7eb;     /* 重要:使用 #e5e7eb,不是 #e0e0e0 */
      border-radius: 8px;
      font-size: 14px;
      box-sizing: border-box;
      transition: all 0.2s;
    }
       
    input:focus, textarea:focus, select:focus {
      outline: none;
      border-color: #3b82f6;
      box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);  /* 重要:焦点阴影 */
    }
  2. 按钮基础样式

    .btn {
      padding: 12px 18px;            /* 注意:是 18px,不是 20px */
      border: none;                  /* 重要:按钮无边框 */
      border-radius: 8px;
      font-size: 13px;               /* 注意:是 13px,不是 14px */
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s;
    }
       
    .btn:hover:not(:disabled) {
      transform: translateY(-1px);   /* 重要:hover 向上移动 */
    }
       
    .btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  3. 小按钮样式

    .btn-small {
      padding: 4px 12px;             /* 用于工具按钮 */
      font-size: 12px;
    }
  4. 按钮颜色变体

    /* 主要按钮 - 蓝色 */
    .btn-primary {
      background: #3b82f6;
      color: white;
    }
    .btn-primary:hover {
      background: #2563eb;
    }
       
    /* 次要按钮 - 灰色 */
    .btn-secondary {
      background: #6b7280;
      color: white;
    }
    .btn-secondary:hover {
      background: #4b5563;
    }
       
    /* 成功按钮 - 绿色 */
    .btn-success {
      background: #10b981;
      color: white;
    }
    .btn-success:hover {
      background: #059669;
    }
       
    /* 危险按钮 - 红色 */
    .btn-danger {
      background: #ef4444;
      color: white;
    }
    .btn-danger:hover {
      background: #dc2626;
    }
       
    /* 警告按钮 - 橙色 */
    .btn-warning {
      background: #f59e0b;
      color: white;
    }
    .btn-warning:hover {
      background: #d97706;
    }
       
    /* 信息按钮 - 青色 */
    .btn-info {
      background: #06b6d4;
      color: white;
    }
    .btn-info:hover {
      background: #0891b2;
    }
布局规范
  1. 间距系统

    • 组件间距:20px
    • 内边距:20-24px
    • 小间距:12px
    • 表单元素间距:16px
  2. 圆角统一

    • 容器:16px
    • 卡片:12px
    • 输入框/按钮:8px
    • 小元素:6px
  3. 响应式设计

    @media (max-width: 768px) {
      .playground-container {
        padding: 20px;
      }
         
      .grid {
        grid-template-columns: 1fr;
      }
    }
特殊元素
  1. 统计卡片

    .stat-item {
      display: flex;
      flex-direction: column;
      gap: 8px;
      padding: 16px;
      background: #f9fafb;
      border-radius: 8px;
      border: 2px solid #e5e7eb;
      text-align: center;
    }
       
    .stat-label {
      font-size: 11px;
      color: #6b7280;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }
       
    .stat-value {
      font-size: 20px;              /* 统计数值使用 20px */
      font-weight: 700;
      color: #3b82f6;               /* 重要:统计值使用主色调 */
      font-family: monospace;       /* 重要:数字使用等宽字体 */
    }
  2. 日志区域(推荐使用整体框方式)

    .log-content {
      background: #f9fafb;
      border: 2px solid #e5e7eb;
      border-radius: 8px;
      padding: 18px;
      max-height: 320px;
      overflow-y: auto;
      font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
      font-size: 13px;
      line-height: 1.6;
    }
       
    .log-item {
      padding: 4px 0;
      color: #374151;
      word-break: break-word;
    }
       
    .log-empty {
      color: #9ca3af;
      text-align: center;
      padding: 24px;
      font-size: 13px;
    }
  3. 结果展示

    .result-card {
      padding: 18px;
      background: #f9fafb;
      border: 2px solid #e5e7eb;
      border-radius: 8px;
      font-family: monospace;
      font-size: 13px;
      line-height: 1.6;
    }
  4. 代码块

    pre, code {
      background: #1e293b;
      color: #e2e8f0;
      padding: 18px;
      border-radius: 8px;
      font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
      font-size: 13px;
      line-height: 1.6;
    }
  5. 状态指示

    .status-indicator {
      display: inline-block;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      margin-right: 8px;
    }
       
    .status-success { background: #10b981; }
    .status-error { background: #ef4444; }
    .status-warning { background: #f59e0b; }
    .status-info { background: #3b82f6; }
禁止事项
  1. ❌ 不使用 emoji

    • 不在 UI 中使用 emoji 图标
    • 使用简洁的文字描述代替
    • 必要时使用 Unicode 符号(如 ✓ ✗)
  2. ❌ 不使用渐变色背景(按钮除外)

    • 避免在卡片上使用渐变
    • 使用纯色或微妙的线性渐变
  3. ❌ 不使用过大的字体

    • 标题 15-16px
    • 正文 14px
    • 代码 13px
    • label 13px
  4. ❌ 不使用左侧色块装饰

    • 避免 border-left 彩色边框
    • 使用统一的边框样式
组件模板
<script setup lang="ts">
import { ref } from 'vue'
import { functionName } from '../../../src/functions/category/functionName'

// 组件逻辑
const input = ref('')
const result = ref('')

function handleAction() {
  result.value = functionName(input.value)
}
</script>

<template>
  <div class="function-playground">
    <!-- 配置区域 -->
    <div class="config-section">
      <h4>配置选项</h4>
      <div class="input-group">
        <label>输入</label>
        <input v-model="input" type="text" placeholder="请输入..." />
      </div>
      <button @click="handleAction" class="btn-primary">执行</button>
    </div>

    <!-- 结果区域 -->
    <div class="result-section" v-if="result">
      <h4>结果</h4>
      <div class="result-card">{{ result }}</div>
    </div>

    <!-- 说明区域 -->
    <div class="description-section">
      <h4>功能说明</h4>
      <p>函数功能的简要说明...</p>
    </div>
  </div>
</template>

<style scoped>
.function-playground {
  width: 100%;
  padding: 24px;
  background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
  border-radius: 16px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
  box-sizing: border-box;
}

.config-section,
.result-section,
.description-section {
  background: white;
  border-radius: 12px;
  padding: 20px;
  margin-bottom: 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}

h4 {
  margin: 0 0 16px 0;
  font-size: 15px;
  font-weight: 600;
  color: #374151;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.input-group {
  margin-bottom: 16px;
}

label {
  display: block;
  margin-bottom: 8px;
  font-size: 13px;
  font-weight: 600;
  color: #6b7280;
}

input {
  width: 100%;
  padding: 12px 14px;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-size: 14px;
  box-sizing: border-box;
  transition: all 0.2s;
}

input:focus {
  outline: none;
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

button {
  padding: 12px 20px;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-primary {
  background: #3b82f6;
  color: white;
}

.btn-primary:hover:not(:disabled) {
  transform: translateY(-1px);
}

.result-card {
  padding: 18px;
  background: #f9fafb;
  border: 2px solid #e5e7eb;
  border-radius: 8px;
  font-family: monospace;
  font-size: 13px;
  line-height: 1.6;
  color: #374151;
}

p {
  margin: 0;
  font-size: 14px;
  line-height: 1.6;
  color: #6b7280;
}

@media (max-width: 768px) {
  .function-playground {
    padding: 20px;
  }
}
</style>

开发注意事项:

  • ✅ 使用 ../../../src/ 导入实际函数,不要 mock
  • ✅ 文件末尾不要有多余空行
  • ✅ 所有输入框添加 box-sizing: border-boxmin-width: 0
  • ✅ 提供实时交互效果
  • ✅ 遵循统一的设计规范
  • ✅ 使用语义化的类名
  • ✅ 添加响应式设计
  • ✅ 保持视觉一致性

4. 侧边栏配置

docs/.vitepress/config.ts 中添加菜单项:

{
  text: '模块名',
  collapsed: true,
  items: [
    { text: 'functionName', link: '/api/category/functionName' },
    // ... 其他函数
  ]
}

5. 文档检查清单

添加新函数后,必须确认:

  • [ ] 函数文档已创建(docs/api/category/functionName.md
  • [ ] 文档包含所有必需部分(函数描述、函数签名、参数表、返回值、工作原理;有 deps 时含前置依赖)
  • [ ] Playground 组件已创建(浏览器端函数)
  • [ ] 组件正确导入实际函数(不是 mock)
  • [ ] 侧边栏配置已更新
  • [ ] 运行 npm run docs:dev 验证无 404 错误
  • [ ] 页面可以正常访问和交互
  • [ ] 组件末尾无多余空行

6. 常见问题

Q: 页面显示 404?

  • 检查文件名大小写是否正确
  • 检查侧边栏配置路径是否正确
  • 检查 Playground 组件是否有语法错误
  • 清除缓存并重启开发服务器

Q: Playground 不显示?

  • 检查组件文件名是否符合命名规范
  • 检查组件末尾是否有多余空行
  • 检查是否正确导入实际函数

Q: Node.js 端函数需要 Playground 吗?

  • 不需要,只需要详细的代码示例即可

通过这种架构设计,shared 包实现了真正的环境无关性,可以在任何 JavaScript/TypeScript 环境中使用,同时保持了良好的类型安全和开发体验。