Skip to content

架构设计与实现原理

Shared Library 是一个现代化的 TypeScript 工具函数库,采用创新的架构设计,解决了传统工具库的诸多痛点。本文将深入讲解库的核心特性是如何实现的。

核心特性概览

  • 🌐 环境无关 - 依赖注入设计,跨平台运行
  • 📦 单函数导出 - 每个函数独立模块,极致 Tree-Shaking
  • 🔍 类型完备 - 100% TypeScript,完整类型推导
  • 🚀 零依赖 - 纯净实现,无运行时依赖

让我们深入了解每个特性的实现原理。

1. 单函数导出架构

实现原理

传统工具库通常使用桶导出(Barrel Export)模式:

typescript
// ❌ 传统方式 - index.ts 统一导出
export { convertColor } from './convertColor'
export { validateColor } from './validateColor'
export { extractColors } from './extractColors'
// ... 导出所有函数

// 使用时
import { convertColor } from 'library' // 可能引入整个模块

这种方式的问题:

  • 打包工具需要分析整个入口文件
  • 容易产生副作用,影响 Tree-Shaking
  • 函数之间的依赖关系不清晰

Shared Library 的解决方案

我们通过 package.jsonexports 字段,为每个函数定义独立的导出路径:

json
{
  "exports": {
    "./functions/color/convertColor": "./dist/functions/color/convertColor.js",
    "./functions/color/validateColor": "./dist/functions/color/colorValidation.js",
    "./hooks/useCache": "./dist/hooks/useCache.js",
    "./constants/colorPatterns": "./dist/constants/colorPatterns.js"
  }
}

这样每个函数都是真正独立的入口点

typescript
// ✅ 我们的方式 - 直接导入函数模块
import { convertColor } from 'zcw-shared/functions/color/convertColor'
// 只会打包这一个函数文件及其直接依赖

构建配置

使用自动化脚本生成 exports 字段:

typescript
// scripts/generate-exports.ts
import { walkDir } from './utils'

const exports: Record<string, string> = {}

// 扫描所有函数文件
const files = walkDir('src/functions')
files.forEach(file => {
  const exportPath = file.replace('src/', './').replace('.ts', '')
  const distPath = file.replace('src/', './dist/').replace('.ts', '.js')
  exports[exportPath] = distPath
})

// 写入 package.json
updatePackageJson({ exports })

优势对比

特性传统桶导出单函数导出
Tree-Shaking依赖打包工具100% 保证
包体积可能包含未使用代码只包含实际使用的函数
构建速度需要分析整个入口直接定位到函数
依赖关系不清晰一目了然

2. 环境无关 - 依赖注入设计

问题背景

传统的工具库通常直接使用全局对象:

typescript
// ❌ 耦合特定环境
export function compressImage(file: File) {
  const canvas = document.createElement('canvas')  // 依赖浏览器 document
  const ctx = canvas.getContext('2d')
  // ...
}

这导致:

  • 只能在浏览器环境运行
  • 无法在 Node.js 中使用(即使有 jsdom)
  • 难以进行单元测试(需要模拟全局对象)

依赖注入实现

核心原则:将环境相关的 API 作为函数参数传入

typescript
// ✅ 依赖注入 - 环境无关
export interface CompressImageDeps {
  createElement: Document['createElement']
}

export function compressImage(
  file: File,
  options: CompressOptions,
  deps: CompressImageDeps
) {
  const canvas = deps.createElement('canvas')
  const ctx = canvas.getContext('2d')
  // ...
}

跨环境使用

同一份代码可以在不同环境运行:

typescript
// 浏览器环境
import { compressImage } from 'zcw-shared/functions/image/compressToTargetSize'
const result = await compressImage(file, options, {
  createElement: document.createElement.bind(document)
})

// Node.js 环境
import { JSDOM } from 'jsdom'
import { compressImage } from 'zcw-shared/functions/image/compressToTargetSize'

const dom = new JSDOM()
const result = await compressImage(file, options, {
  createElement: dom.window.document.createElement.bind(dom.window.document)
})

// Electron 环境
import { compressImage } from 'zcw-shared/functions/image/compressToTargetSize'
const result = await compressImage(file, options, {
  createElement: document.createElement.bind(document)
})

类型定义设计

使用 TypeScript 接口定义最小化的环境 API 需求:

typescript
// 只提取需要的函数
export interface CompressImageDeps {
  createElement: Document['createElement']
}

export function compressImage(
  file: File,
  options: CompressOptions,
  deps: CompressImageDeps  // 最小化依赖
) {
  // ...
}

这样做的好处:

  • 清晰地表明函数需要哪些 API
  • 便于 Mock 和测试
  • 支持任何实现了该接口的环境
  • 避免传入大对象,只传入真正需要的函数

3. TypeScript 类型系统

100% TypeScript 编写

不同于很多库是 JavaScript 写的然后加 .d.ts,我们从源码开始就是 TypeScript:

typescript
// src/functions/color/convertColor.ts
export function convertColor(
  color: string,
  targetFormat: 'hex' | 'rgb' | 'hsl',
  options?: ConvertOptions
): string {
  // TypeScript 源码
}

类型推导优势

原生 TypeScript 可以提供更强大的类型推导:

typescript
import { convertColor } from 'zcw-shared/functions/color/convertColor'

// 类型自动推导
const result = convertColor('#ff0000', 'rgb')
//    ^? string

// 错误会在编译时捕获
const invalid = convertColor('#ff0000', 'invalid')
//                                       ~~~~~~~~~ 
// 类型 "invalid" 不能赋值给类型 "hex" | "rgb" | "hsl"

复杂类型的精确定义

typescript
// types/color.d.ts
export type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'rgba' | 'hsla'

export interface ConvertOptions {
  alpha?: boolean
  precision?: number
  uppercase?: boolean
}

// 条件类型 - 根据输入推导输出
export type ConvertResult<F extends ColorFormat> = 
  F extends 'hex' ? `#${string}` :
  F extends 'rgb' ? `rgb(${string})` :
  F extends 'hsl' ? `hsl(${string})` :
  string

类型文件的独立导出

类型定义也可以单独导入:

typescript
import type { ColorFormat, ConvertOptions } from 'zcw-shared/types/color'

// 只导入类型,不会增加运行时体积
const format: ColorFormat = 'hex'

设计原则总结

1. 职责单一

每个函数只做一件事,做好一件事。

2. 显式优于隐式

依赖关系明确,不隐藏任何依赖。

3. 类型安全优先

充分利用 TypeScript 的类型系统,在编译时捕获错误。

4. 环境无关

通过依赖注入实现跨平台,不假设运行环境。

5. 零运行时依赖

纯净实现,减少依赖链复杂度。

对比其他工具库

特性LodashRamdaShared Library
导入方式桶导出桶导出单函数导出
Tree-Shaking需要插件部分支持原生支持
环境无关✅ 依赖注入
TypeScript.d.ts.d.ts原生 TS
运行时依赖000
类型导出✅ 独立导出

下一步

现在你已经了解了 Shared Library 的架构设计和实现原理,可以: