架构设计与实现原理
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.json 的 exports 字段,为每个函数定义独立的导出路径:
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. 零运行时依赖
纯净实现,减少依赖链复杂度。
对比其他工具库
| 特性 | Lodash | Ramda | Shared Library |
|---|---|---|---|
| 导入方式 | 桶导出 | 桶导出 | 单函数导出 |
| Tree-Shaking | 需要插件 | 部分支持 | 原生支持 |
| 环境无关 | ❌ | ❌ | ✅ 依赖注入 |
| TypeScript | .d.ts | .d.ts | 原生 TS |
| 运行时依赖 | 0 | 0 | 0 |
| 类型导出 | ❌ | ❌ | ✅ 独立导出 |
下一步
现在你已经了解了 Shared Library 的架构设计和实现原理,可以: