跳到主要内容

前端 SDK 通用架构设计

一、需求分析

1.1 什么是前端 SDK

前端 SDK(Software Development Kit)是为开发者提供的一套封装好的工具包,用于快速集成某项功能到其应用中。常见的前端 SDK 包括:

SDK 类型典型产品核心功能
监控 SDKSentry、Fundebug、ARMS错误捕获、性能监控、用户行为追踪
分析 SDKGoogle Analytics、Mixpanel埋点、用户画像、转化分析
播放器 SDKvideo.js、DPlayer、hls.js视频播放、弹幕、自适应码率
IM SDK环信、融云、腾讯云 IM即时通讯、消息收发、会话管理
地图 SDK高德 JS API、Mapbox GL地图渲染、路径规划、地理编码
编辑器 SDKProseMirror、Slate、Tiptap富文本编辑、协同、插件扩展
支付 SDKStripe.js、微信 JS-SDK支付流程、安全验签

1.2 核心设计目标

面试要点

面试中回答「如何设计一个 SDK」时,先阐明设计目标,再展开架构设计,能体现系统性思维。

设计目标说明实现手段
最小化 API对外暴露尽可能少的接口,降低学习成本门面模式、链式调用
渐进式增强核心功能开箱即用,高级功能按需引入插件系统、可选扩展
零依赖 / 轻依赖不引入大型第三方库,减少与宿主冲突自研核心模块、外部依赖可选注入
Tree-shakable未使用的模块不打入最终产物ESM 导出、sideEffects: false
类型安全完善的 TypeScript 类型支持泛型配置、声明合并、类型推导
环境兼容浏览器、Node.js、小程序等多端运行适配层、Platform 抽象
稳定可靠插件崩溃不影响核心,全局错误兜底错误隔离、降级策略
可观测日志、调试工具完善日志等级、DevTools 插件

1.3 非功能需求

需求指标实现方式
体积Core < 5KB gzippedTree Shaking、代码分割
性能初始化 < 5ms,不阻塞主线程异步初始化、延迟加载
兼容性支持 IE11+(可选)或现代浏览器Polyfill 按需注入、构建降级
安全性防止 XSS、CSRF,数据脱敏输入校验、CSP 兼容

二、整体架构

2.1 SDK 架构全景

2.2 分层架构说明

SDK 通常采用分层架构,从上到下分为四层:

  1. 用户层(User Layer):对外暴露的 API,简洁易用,通常只有 inituseondestroy 等少量方法
  2. 核心层(Core Layer):SDK 的心脏,包含事件总线、配置管理、生命周期、日志、错误边界等基础设施
  3. 插件层(Plugin Layer):所有业务功能以插件形式实现,通过插件管理器统一注册、调度、卸载
  4. 平台适配层(Platform Layer):抽象底层 API 差异(DOM、网络请求、存储等),实现多端运行
设计哲学

内核尽可能小,功能尽可能由插件提供。这是现代 SDK 架构的核心哲学,参考 Vite 的插件化设计、Sentry 的 Integration 机制。内核只负责调度,不负责具体业务


三、核心模块设计

3.1 Core 内核

内核是 SDK 的中央调度器,串联所有模块。

core/sdk.ts
interface SDKOptions<TConfig extends BaseConfig = BaseConfig> {
/** SDK 名称 */
name: string;
/** SDK 版本 */
version: string;
/** 用户配置 */
config: TConfig;
/** 初始插件列表 */
plugins?: Plugin<TConfig>[];
}

class SDK<TConfig extends BaseConfig = BaseConfig> {
readonly name: string;
readonly version: string;

// 核心模块:通过组合而非继承的方式组织
private configManager: ConfigManager<TConfig>;
private eventEmitter: EventEmitter;
private pluginManager: PluginManager<TConfig>;
private lifecycleManager: LifecycleManager;
private logger: Logger;
private errorBoundary: ErrorBoundary;

private initialized = false;

constructor(options: SDKOptions<TConfig>) {
this.name = options.name;
this.version = options.version;

// 按依赖顺序初始化核心模块
this.logger = new Logger(options.config.logLevel ?? 'warn');
this.errorBoundary = new ErrorBoundary(this.logger);
this.eventEmitter = new EventEmitter();
this.configManager = new ConfigManager(options.config);
this.lifecycleManager = new LifecycleManager(this.eventEmitter);
this.pluginManager = new PluginManager(this);
}

/** 初始化 SDK */
async init(): Promise<void> {
if (this.initialized) {
this.logger.warn('SDK already initialized');
return;
}

this.lifecycleManager.emit('beforeInit');

try {
// 按依赖关系排序后,依次初始化插件
await this.pluginManager.initAll();
this.initialized = true;
this.lifecycleManager.emit('initialized');
this.logger.info(`${this.name}@${this.version} initialized`);
} catch (error) {
this.errorBoundary.capture(error as Error, { phase: 'init' });
throw error;
}
}

/** 注册插件 */
use(plugin: Plugin<TConfig>): this {
this.pluginManager.register(plugin);
return this; // 链式调用
}

/** 监听事件 */
on(event: string, handler: EventHandler): this {
this.eventEmitter.on(event, handler);
return this;
}

/** 触发事件 */
emit(event: string, ...args: unknown[]): void {
this.eventEmitter.emit(event, ...args);
}

/** 获取配置 */
getConfig(): Readonly<TConfig> {
return this.configManager.getConfig();
}

/** 更新配置 */
setConfig(partial: Partial<TConfig>): void {
this.configManager.update(partial);
this.eventEmitter.emit('configChange', this.configManager.getConfig());
}

/** 获取日志实例 */
getLogger(): Logger {
return this.logger;
}

/** 销毁 SDK */
async destroy(): Promise<void> {
this.lifecycleManager.emit('beforeDestroy');
await this.pluginManager.destroyAll();
this.eventEmitter.removeAllListeners();
this.initialized = false;
this.lifecycleManager.emit('destroyed');
}
}

3.2 事件总线(EventEmitter)

事件总线是模块间通信的核心机制,解耦各模块间的直接依赖。

core/event-emitter.ts
type EventHandler = (...args: unknown[]) => void | Promise<void>;

class EventEmitter {
private listeners = new Map<string, Set<EventHandler>>();
private maxListeners = 50;

/** 注册事件监听 */
on(event: string, handler: EventHandler): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}

const handlers = this.listeners.get(event)!;

if (handlers.size >= this.maxListeners) {
console.warn(
`Event "${event}" has ${handlers.size} listeners. Possible memory leak.`
);
}

handlers.add(handler);

// 返回取消函数,方便解绑
return () => this.off(event, handler);
}

/** 一次性监听 */
once(event: string, handler: EventHandler): () => void {
const wrapper: EventHandler = (...args) => {
this.off(event, wrapper);
return handler(...args);
};
return this.on(event, wrapper);
}

/** 取消监听 */
off(event: string, handler: EventHandler): void {
this.listeners.get(event)?.delete(handler);
}

/** 触发事件 */
emit(event: string, ...args: unknown[]): void {
const handlers = this.listeners.get(event);
if (!handlers) return;

for (const handler of handlers) {
try {
handler(...args);
} catch (error) {
console.error(`Error in event handler for "${event}":`, error);
}
}
}

/** 异步触发(等待所有 handler 完成) */
async emitAsync(event: string, ...args: unknown[]): Promise<void> {
const handlers = this.listeners.get(event);
if (!handlers) return;

// 串行执行,保证执行顺序(类似 Koa 中间件)
for (const handler of handlers) {
await handler(...args);
}
}

/** 清除所有监听 */
removeAllListeners(event?: string): void {
if (event) {
this.listeners.delete(event);
} else {
this.listeners.clear();
}
}

/** 获取事件监听数量 */
listenerCount(event: string): number {
return this.listeners.get(event)?.size ?? 0;
}
}
常见坑点
  1. 内存泄漏:忘记 off 导致监听器不断累积。SDK 应提供 maxListeners 限制并输出警告
  2. 异常隔离:某个 handler 抛错不应影响其他 handler 的执行,需 try-catch 包裹
  3. 执行顺序:同步 emit 保证注册顺序,异步 emitAsync 串行执行防止竞态

3.3 生命周期管理

SDK 和插件都有自己的生命周期,统一管理可以确保初始化和销毁的顺序正确。

core/lifecycle.ts
/** SDK 生命周期状态 */
enum LifecycleState {
Created = 'created',
Initializing = 'initializing',
Initialized = 'initialized',
Running = 'running',
Destroying = 'destroying',
Destroyed = 'destroyed',
}

/** 生命周期事件类型 */
type LifecycleEvent =
| 'beforeInit'
| 'initialized'
| 'beforeDestroy'
| 'destroyed'
| 'configChange'
| 'error';

class LifecycleManager {
private state: LifecycleState = LifecycleState.Created;
private eventEmitter: EventEmitter;

constructor(eventEmitter: EventEmitter) {
this.eventEmitter = eventEmitter;
}

getState(): LifecycleState {
return this.state;
}

emit(event: LifecycleEvent, ...args: unknown[]): void {
// 根据事件自动切换状态
const stateMap: Partial<Record<LifecycleEvent, LifecycleState>> = {
beforeInit: LifecycleState.Initializing,
initialized: LifecycleState.Running,
beforeDestroy: LifecycleState.Destroying,
destroyed: LifecycleState.Destroyed,
};

if (stateMap[event]) {
this.state = stateMap[event]!;
}

this.eventEmitter.emit(`lifecycle:${event}`, ...args);
}

/** 监听生命周期事件 */
on(event: LifecycleEvent, handler: EventHandler): () => void {
return this.eventEmitter.on(`lifecycle:${event}`, handler);
}
}

四、插件系统设计

插件系统是 SDK 架构中最核心的扩展机制。一个优秀的插件系统应该做到:插件间解耦、依赖可排序、错误可隔离、通信有规范

4.1 插件接口定义

core/plugin.ts
/** 插件元信息 */
interface PluginMeta {
/** 插件唯一标识 */
name: string;
/** 插件版本 */
version: string;
/** 依赖的其他插件名称 */
dependencies?: string[];
/** 插件描述 */
description?: string;
}

/** 插件上下文 —— 插件可访问的 SDK 能力 */
interface PluginContext<TConfig extends BaseConfig = BaseConfig> {
/** SDK 配置 */
getConfig(): Readonly<TConfig>;
/** 事件总线 */
on(event: string, handler: EventHandler): () => void;
emit(event: string, ...args: unknown[]): void;
/** 日志 */
logger: Logger;
/** 获取其他插件实例(用于插件间通信) */
getPlugin<T extends Plugin>(name: string): T | undefined;
}

/** 插件接口 —— 所有插件必须实现 */
interface Plugin<TConfig extends BaseConfig = BaseConfig> {
/** 插件元信息 */
meta: PluginMeta;
/** 安装钩子:插件初始化逻辑 */
install(ctx: PluginContext<TConfig>): void | Promise<void>;
/** 卸载钩子:清理资源 */
uninstall?(): void | Promise<void>;
}

4.2 插件管理器

core/plugin-manager.ts
class PluginManager<TConfig extends BaseConfig = BaseConfig> {
private plugins = new Map<string, Plugin<TConfig>>();
private installedPlugins = new Set<string>();
private sdk: SDK<TConfig>;

constructor(sdk: SDK<TConfig>) {
this.sdk = sdk;
}

/** 注册插件 */
register(plugin: Plugin<TConfig>): void {
const { name } = plugin.meta;

if (this.plugins.has(name)) {
this.sdk.getLogger().warn(`Plugin "${name}" already registered, skipping`);
return;
}

this.plugins.set(name, plugin);
this.sdk.getLogger().debug(`Plugin "${name}" registered`);
}

/** 按依赖拓扑排序后初始化所有插件 */
async initAll(): Promise<void> {
const sorted = this.topologicalSort();
const ctx = this.createContext();

for (const plugin of sorted) {
await this.installPlugin(plugin, ctx);
}
}

/** 安装单个插件(带错误隔离) */
private async installPlugin(
plugin: Plugin<TConfig>,
ctx: PluginContext<TConfig>
): Promise<void> {
const { name } = plugin.meta;

try {
await plugin.install(ctx);
this.installedPlugins.add(name);
this.sdk.getLogger().debug(`Plugin "${name}" installed`);
} catch (error) {
// 关键:插件安装失败不应导致整个 SDK 崩溃
this.sdk.getLogger().error(`Plugin "${name}" install failed:`, error);
this.sdk.emit('pluginError', { plugin: name, error, phase: 'install' });
}
}

/** 拓扑排序:解决插件依赖问题 */
private topologicalSort(): Plugin<TConfig>[] {
const result: Plugin<TConfig>[] = [];
const visited = new Set<string>();
const visiting = new Set<string>(); // 检测循环依赖

const visit = (name: string) => {
if (visited.has(name)) return;

if (visiting.has(name)) {
throw new Error(`Circular dependency detected: ${name}`);
}

visiting.add(name);

const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Missing dependency: "${name}"`);
}

// 先递归处理依赖
for (const dep of plugin.meta.dependencies ?? []) {
visit(dep);
}

visiting.delete(name);
visited.add(name);
result.push(plugin);
};

for (const name of this.plugins.keys()) {
visit(name);
}

return result;
}

/** 创建插件上下文 */
private createContext(): PluginContext<TConfig> {
return {
getConfig: () => this.sdk.getConfig(),
on: (event, handler) => {
this.sdk.on(event, handler);
return () => {}; // 简化,实际返回取消函数
},
emit: (event, ...args) => this.sdk.emit(event, ...args),
logger: this.sdk.getLogger(),
getPlugin: <T extends Plugin>(name: string) =>
this.plugins.get(name) as T | undefined,
};
}

/** 销毁所有插件(逆序销毁) */
async destroyAll(): Promise<void> {
const reversed = [...this.installedPlugins].reverse();

for (const name of reversed) {
const plugin = this.plugins.get(name);
try {
await plugin?.uninstall?.();
this.sdk.getLogger().debug(`Plugin "${name}" uninstalled`);
} catch (error) {
this.sdk.getLogger().error(`Plugin "${name}" uninstall failed:`, error);
}
}

this.installedPlugins.clear();
}

/** 获取已安装的插件 */
getPlugin<T extends Plugin>(name: string): T | undefined {
return this.plugins.get(name) as T | undefined;
}
}

4.3 插件生命周期

4.4 插件示例:性能监控插件

plugins/performance-plugin.ts
const PerformancePlugin: Plugin<MonitorConfig> = {
meta: {
name: 'performance',
version: '1.0.0',
description: '性能数据采集插件',
dependencies: [], // 无依赖
},

install(ctx) {
const config = ctx.getConfig();
const logger = ctx.logger;

// 采集 Web Vitals
if (typeof window !== 'undefined' && 'PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
ctx.emit('performance:metric', {
name: entry.name,
value: (entry as PerformanceEntry & { value?: number }).value
?? entry.duration,
timestamp: Date.now(),
});
}
});

try {
observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.observe({ type: 'first-input', buffered: true });
observer.observe({ type: 'layout-shift', buffered: true });
logger.info('PerformancePlugin: observing Web Vitals');
} catch (e) {
logger.warn('PerformancePlugin: some metrics not supported');
}

// 存储 observer 引用,方便卸载时断开
(this as Plugin & { _observer?: PerformanceObserver })._observer = observer;
}

// 监听配置变更
ctx.on('configChange', (newConfig: unknown) => {
logger.debug('PerformancePlugin: config updated', newConfig);
});
},

uninstall() {
// 清理 PerformanceObserver
const self = this as Plugin & { _observer?: PerformanceObserver };
self._observer?.disconnect();
self._observer = undefined;
},
};

4.5 插件间通信

插件间通信有两种模式:

模式说明适用场景
事件驱动通过事件总线发布/订阅松耦合、广播式通信
直接引用通过 ctx.getPlugin() 获取实例紧耦合、需要调用特定方法
plugins/reporter-plugin.ts
const ReporterPlugin: Plugin<MonitorConfig> = {
meta: {
name: 'reporter',
version: '1.0.0',
dependencies: ['performance'], // 依赖 performance 插件
},

install(ctx) {
const buffer: unknown[] = [];
const config = ctx.getConfig();

// 方式一:事件驱动 —— 监听其他插件发出的事件
ctx.on('performance:metric', (data: unknown) => {
buffer.push(data);

if (buffer.length >= (config.batchSize ?? 10)) {
flush();
}
});

// 方式二:直接引用
const perfPlugin = ctx.getPlugin('performance');
if (perfPlugin) {
ctx.logger.debug('ReporterPlugin: performance plugin found');
}

function flush(): void {
if (buffer.length === 0) return;
const batch = buffer.splice(0);

// 使用 sendBeacon 保证页面关闭时数据不丢失
if (navigator.sendBeacon) {
navigator.sendBeacon(
config.reportUrl ?? '/api/report',
JSON.stringify(batch)
);
} else {
fetch(config.reportUrl ?? '/api/report', {
method: 'POST',
body: JSON.stringify(batch),
keepalive: true,
}).catch(() => {});
}
}

// 页面卸载前 flush 一次
window.addEventListener('beforeunload', flush);
},
};

五、配置管理

5.1 配置合并策略

SDK 配置通常采用默认配置 + 用户配置深度合并的方式:

core/config-manager.ts
interface BaseConfig {
/** 是否启用调试模式 */
debug?: boolean;
/** 日志等级 */
logLevel?: LogLevel;
/** 环境:自动检测或手动指定 */
environment?: 'development' | 'staging' | 'production';
/** 应用标识 */
appId?: string;
}

/** 默认配置 */
const DEFAULT_CONFIG: BaseConfig = {
debug: false,
logLevel: 'warn',
environment: detectEnvironment(),
};

class ConfigManager<TConfig extends BaseConfig = BaseConfig> {
private config: TConfig;
private initialConfig: TConfig;

constructor(userConfig: TConfig) {
// 深度合并:默认配置 ← 用户配置
this.config = this.deepMerge(
{ ...DEFAULT_CONFIG } as TConfig,
userConfig
);
// 保存初始配置,支持 reset
this.initialConfig = structuredClone(this.config);
}

/** 获取当前配置(只读) */
getConfig(): Readonly<TConfig> {
return Object.freeze({ ...this.config });
}

/** 运行时更新配置 */
update(partial: Partial<TConfig>): void {
this.config = this.deepMerge(this.config, partial as TConfig);
}

/** 重置为初始配置 */
reset(): void {
this.config = structuredClone(this.initialConfig);
}

/** 深度合并工具函数 */
private deepMerge<T extends Record<string, unknown>>(
target: T,
source: Partial<T>
): T {
const result = { ...target };

for (const key in source) {
const sourceVal = source[key];
const targetVal = result[key];

if (
sourceVal !== null &&
typeof sourceVal === 'object' &&
!Array.isArray(sourceVal) &&
targetVal !== null &&
typeof targetVal === 'object' &&
!Array.isArray(targetVal)
) {
(result as Record<string, unknown>)[key] = this.deepMerge(
targetVal as Record<string, unknown>,
sourceVal as Record<string, unknown>
);
} else if (sourceVal !== undefined) {
(result as Record<string, unknown>)[key] = sourceVal;
}
}

return result;
}
}

/** 自动检测环境 */
function detectEnvironment(): 'development' | 'staging' | 'production' {
if (typeof window === 'undefined') return 'production';

const hostname = window.location.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') return 'development';
if (hostname.includes('staging') || hostname.includes('test')) return 'staging';
return 'production';
}

5.2 配置校验

注意

用户传入的配置可能不合法,SDK 应在初始化阶段进行校验,给出明确的错误提示,而不是在运行时才报错。

core/config-validator.ts
type ValidationRule<T> = {
field: keyof T;
type?: string;
required?: boolean;
validator?: (value: unknown) => boolean;
message: string;
};

function validateConfig<T extends BaseConfig>(
config: T,
rules: ValidationRule<T>[]
): void {
const errors: string[] = [];

for (const rule of rules) {
const value = config[rule.field];

if (rule.required && (value === undefined || value === null)) {
errors.push(`[Config] "${String(rule.field)}" is required. ${rule.message}`);
continue;
}

if (value !== undefined && rule.type && typeof value !== rule.type) {
errors.push(
`[Config] "${String(rule.field)}" expected ${rule.type}, got ${typeof value}`
);
}

if (value !== undefined && rule.validator && !rule.validator(value)) {
errors.push(`[Config] "${String(rule.field)}" validation failed: ${rule.message}`);
}
}

if (errors.length > 0) {
throw new Error(`SDK Config Validation Failed:\n${errors.join('\n')}`);
}
}

六、日志与调试

6.1 日志系统

core/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';

const LOG_LEVELS: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
silent: 4,
};

class Logger {
private level: LogLevel;
private prefix: string;
private remoteHandler?: (level: LogLevel, args: unknown[]) => void;

constructor(level: LogLevel = 'warn', prefix: string = '[SDK]') {
this.level = level;
this.prefix = prefix;
}

/** 设置日志等级 */
setLevel(level: LogLevel): void {
this.level = level;
}

/** 设置远程日志上报处理器 */
setRemoteHandler(handler: (level: LogLevel, args: unknown[]) => void): void {
this.remoteHandler = handler;
}

debug(...args: unknown[]): void {
this.log('debug', args);
}

info(...args: unknown[]): void {
this.log('info', args);
}

warn(...args: unknown[]): void {
this.log('warn', args);
}

error(...args: unknown[]): void {
this.log('error', args);
}

private log(level: LogLevel, args: unknown[]): void {
// 等级过滤
if (LOG_LEVELS[level] < LOG_LEVELS[this.level]) return;

const timestamp = new Date().toISOString();
const formattedArgs = [`${this.prefix} [${timestamp}]`, ...args];

// 本地输出
switch (level) {
case 'debug': console.debug(...formattedArgs); break;
case 'info': console.info(...formattedArgs); break;
case 'warn': console.warn(...formattedArgs); break;
case 'error': console.error(...formattedArgs); break;
}

// 远程上报(仅 warn 和 error)
if (this.remoteHandler && LOG_LEVELS[level] >= LOG_LEVELS['warn']) {
try {
this.remoteHandler(level, args);
} catch {
// 远程上报失败不应影响 SDK 运行
}
}
}
}

6.2 开发调试模式

core/devtools.ts
/** 开发模式工具,生产环境自动 Tree Shake */
function enableDevTools<TConfig extends BaseConfig>(sdk: SDK<TConfig>): void {
if (typeof window === 'undefined') return;

// 在 window 上挂载调试对象
const devtools = {
/** 查看当前配置 */
getConfig: () => sdk.getConfig(),
/** 查看 SDK 版本 */
version: sdk.version,
/** 手动触发事件 */
emit: (event: string, ...args: unknown[]) => sdk.emit(event, ...args),
/** 修改日志等级 */
setLogLevel: (level: LogLevel) => sdk.getLogger().setLevel(level),
};

(window as Record<string, unknown>).__SDK_DEVTOOLS__ = devtools;

sdk.getLogger().info(
'DevTools enabled. Access via window.__SDK_DEVTOOLS__'
);
}
调试技巧

生产环境下可以通过 URL 参数临时开启调试模式,例如在 URL 中添加 ?__sdk_debug=true,SDK 检测到后自动将日志等级设为 debug,方便线上排查问题。


七、错误处理

7.1 全局错误边界

core/error-boundary.ts
interface ErrorInfo {
/** 错误发生阶段 */
phase: 'init' | 'runtime' | 'destroy';
/** 相关插件名称 */
plugin?: string;
/** 额外上下文 */
context?: Record<string, unknown>;
}

class ErrorBoundary {
private logger: Logger;
private handlers: Array<(error: Error, info: ErrorInfo) => void> = [];
private errorCount = 0;
private readonly maxErrors = 100; // 防止错误风暴

constructor(logger: Logger) {
this.logger = logger;
}

/** 注册错误处理器 */
onError(handler: (error: Error, info: ErrorInfo) => void): void {
this.handlers.push(handler);
}

/** 捕获错误 */
capture(error: Error, info: ErrorInfo): void {
this.errorCount++;

// 防止错误风暴:超过阈值后只记录不处理
if (this.errorCount > this.maxErrors) {
if (this.errorCount === this.maxErrors + 1) {
this.logger.error(
'Too many errors captured, further errors will be suppressed'
);
}
return;
}

this.logger.error(`[${info.phase}] ${error.message}`, info);

for (const handler of this.handlers) {
try {
handler(error, info);
} catch (handlerError) {
// 错误处理器本身报错,只记录不再抛出
this.logger.error('Error handler threw:', handlerError);
}
}
}

/** 安全执行:包裹可能出错的函数 */
safeExecute<T>(
fn: () => T,
info: ErrorInfo,
fallback?: T
): T | undefined {
try {
return fn();
} catch (error) {
this.capture(error as Error, info);
return fallback;
}
}

/** 安全执行异步函数 */
async safeExecuteAsync<T>(
fn: () => Promise<T>,
info: ErrorInfo,
fallback?: T
): Promise<T | undefined> {
try {
return await fn();
} catch (error) {
this.capture(error as Error, info);
return fallback;
}
}
}

7.2 插件错误隔离策略

示意:插件错误隔离
// 在 PluginManager 的 installPlugin 方法中

private async installPlugin(
plugin: Plugin<TConfig>,
ctx: PluginContext<TConfig>
): Promise<void> {
const { name } = plugin.meta;

// 策略一:错误隔离 —— 单个插件失败不影响其他插件
const result = await this.sdk.errorBoundary.safeExecuteAsync(
() => plugin.install(ctx),
{ phase: 'init', plugin: name }
);

if (result !== undefined) {
this.installedPlugins.add(name);
} else {
// 策略二:降级处理 —— 记录失败插件,后续提供 retry 机制
this.failedPlugins.add(name);
this.sdk.emit('plugin:installFailed', {
plugin: name,
retryable: true,
});
}
}
降级策略

插件安装失败后,SDK 应该:

  1. 记录失败原因,方便开发者排查
  2. 不影响其他插件,保证核心功能可用
  3. 提供重试机制,网络类插件可能只是暂时失败
  4. 通知上层,通过事件或回调让业务方感知

八、版本管理与发布

8.1 Semver 语义化版本

版本段含义升级场景
Major (X.0.0)不兼容的 API 变更删除/重命名 API、改变参数类型
Minor (0.X.0)向后兼容的新功能新增插件、新增配置项
Patch (0.0.X)向后兼容的问题修复Bug 修复、性能优化
SDK 版本管理最佳实践
  • 在 SDK 初始化日志中打印版本号:SDK v1.2.3 initialized
  • 在上报数据中附带 SDK 版本,方便后端区分数据格式
  • 发布 Breaking Change 时提供迁移指南和 codemod 工具
  • 大版本更新同时维护旧版 LTS(Long-Term Support)

8.2 多格式产物

SDK 需要输出多种模块格式,以适配不同的使用场景:

格式文件使用场景特点
ESMdist/index.esm.js现代构建工具(Vite、Webpack 5)支持 Tree Shaking
CJSdist/index.cjs.jsNode.js、旧版 Webpackrequire() 引入
UMDdist/index.umd.jsCDN <script> 引入挂载全局变量
IIFEdist/index.iife.js直接 <script> 引入最简单的引入方式
rollup.config.ts
import { defineConfig } from 'rollup';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import { dts } from 'rollup-plugin-dts';

export default defineConfig([
// 主构建
{
input: 'src/index.ts',
output: [
{ file: 'dist/index.esm.js', format: 'esm', sourcemap: true },
{ file: 'dist/index.cjs.js', format: 'cjs', sourcemap: true },
{
file: 'dist/index.umd.js',
format: 'umd',
name: 'MySDK',
sourcemap: true,
},
{
file: 'dist/index.iife.js',
format: 'iife',
name: 'MySDK',
plugins: [terser()], // IIFE 通常用于 CDN,需要压缩
},
],
plugins: [typescript()],
},
// 类型声明
{
input: 'src/index.ts',
output: { file: 'dist/index.d.ts', format: 'esm' },
plugins: [dts()],
},
]);

8.3 package.json 多入口配置

package.json
{
"name": "@my-org/sdk",
"version": "1.0.0",
"main": "./dist/index.cjs.js",
"module": "./dist/index.esm.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./dist/index.cjs.js",
"types": "./dist/index.d.ts"
},
"./plugins/*": {
"import": "./dist/plugins/*.esm.js",
"require": "./dist/plugins/*.cjs.js",
"types": "./dist/plugins/*.d.ts"
}
},
"sideEffects": false,
"files": ["dist"],
"unpkg": "./dist/index.umd.js",
"jsdelivr": "./dist/index.umd.js"
}

8.4 CDN + npm 双发布


九、体积优化

9.1 Tree Shaking

SDK 的 ESM 导出方式直接影响 Tree Shaking 效果:

src/index.ts — 推荐的导出方式
// 具名导出:支持 Tree Shaking
export { SDK } from './core/sdk';
export { EventEmitter } from './core/event-emitter';
export { Logger } from './core/logger';

// 插件单独导出
export { PerformancePlugin } from './plugins/performance';
export { ReporterPlugin } from './plugins/reporter';
export { ErrorPlugin } from './plugins/error';

// 类型导出(零体积)
export type { Plugin, PluginMeta, PluginContext } from './core/plugin';
export type { BaseConfig } from './core/config-manager';
注意

不要使用桶文件(barrel files)的 export * 重导出,这会导致 Tree Shaking 失效。确保 package.json 中设置 "sideEffects": false

9.2 按需加载插件

按需加载插件示例
// 方式一:静态导入(全量打包)
import { SDK, PerformancePlugin, ReporterPlugin } from '@my-org/sdk';

// 方式二:动态导入(按需加载,推荐)
const sdk = new SDK({ name: 'myApp', version: '1.0.0', config: {} });

// 只有需要时才加载性能监控插件
if (shouldEnablePerformance()) {
const { PerformancePlugin } = await import('@my-org/sdk/plugins/performance');
sdk.use(PerformancePlugin);
}

await sdk.init();

9.3 体积对比参考

SDKCore 体积 (gzipped)全量体积 (gzipped)插件化
Sentry Browser~6 KB~28 KB是(Integrations)
Google Analytics (gtag)~30 KB~80 KB
Mixpanel~15 KB~15 KB
理想 SDK 架构< 3 KB< 15 KB

十、TypeScript 类型设计

10.1 泛型配置

SDK 的配置类型应支持泛型扩展,让用户可以定义自己的配置字段:

types/config.ts
/** 基础配置 */
interface BaseConfig {
debug?: boolean;
logLevel?: LogLevel;
environment?: string;
}

/** 监控 SDK 配置(扩展基础配置) */
interface MonitorConfig extends BaseConfig {
appId: string;
reportUrl: string;
sampleRate?: number;
batchSize?: number;
maxBreadcrumbs?: number;
}

/** 播放器 SDK 配置 */
interface PlayerConfig extends BaseConfig {
container: string | HTMLElement;
src: string;
autoplay?: boolean;
controls?: boolean;
}

// SDK 实例类型随配置类型变化
const monitorSDK = new SDK<MonitorConfig>({
name: 'monitor',
version: '1.0.0',
config: {
appId: 'xxx', // 类型安全:必填
reportUrl: '/api/report', // 类型安全:必填
sampleRate: 0.1, // 类型安全:可选
},
});

10.2 插件类型扩展(声明合并)

利用 TypeScript 的声明合并,让用户安装插件后获得增强的类型提示:

types/plugin-augmentation.ts
// 基础 SDK 事件类型
interface SDKEventMap {
'lifecycle:beforeInit': () => void;
'lifecycle:initialized': () => void;
'lifecycle:beforeDestroy': () => void;
'lifecycle:destroyed': () => void;
'configChange': (config: BaseConfig) => void;
}

// 性能插件通过声明合并扩展事件类型
// 安装 PerformancePlugin 后,用户 on('performance:metric', ...) 会有类型提示
declare module '@my-org/sdk' {
interface SDKEventMap {
'performance:metric': (data: {
name: string;
value: number;
timestamp: number;
}) => void;
'performance:longtask': (duration: number) => void;
}
}

// 强类型的事件监听
class TypedEventEmitter {
on<K extends keyof SDKEventMap>(
event: K,
handler: SDKEventMap[K]
): () => void {
// ...
return () => {};
}

emit<K extends keyof SDKEventMap>(
event: K,
...args: Parameters<SDKEventMap[K]>
): void {
// ...
}
}

10.3 条件类型与工具类型

types/utilities.ts
/** 提取插件的 meta.name 字面量类型 */
type PluginName<P extends Plugin> = P['meta']['name'];

/** 深度只读 */
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

/** 深度可选 */
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

/** 排除 undefined 的配置 */
type RequiredConfig<T extends BaseConfig> = Required<{
[K in keyof T]: NonNullable<T[K]>;
}>;

/** 从配置类型推断 SDK 实例类型 */
type SDKInstance<TConfig extends BaseConfig> = SDK<TConfig> & {
getConfig(): DeepReadonly<TConfig>;
};

十一、实际案例分析

11.1 Sentry SDK 架构

Sentry 是前端监控领域最知名的 SDK,其架构设计极具参考价值:

Sentry 的核心设计思想:

概念说明对应本文架构
Hub全局单例,管理 Client 和 ScopeCore 内核
Scope上下文信息(用户、tags、breadcrumbs)ConfigManager
Client负责事件处理和发送PluginManager + Reporter
Transport底层传输抽象(fetch/XHR)Platform 适配层
Integration插件系统Plugin 插件系统
Sentry SDK 使用示例
Sentry 初始化
import * as Sentry from '@sentry/react';

Sentry.init({
dsn: 'https://xxx@sentry.io/123',
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration(),
],
tracesSampleRate: 0.1,
replaysSessionSampleRate: 0.1,
});

// 手动捕获错误
try {
dangerousOperation();
} catch (error) {
Sentry.captureException(error, {
tags: { module: 'payment' },
extra: { orderId: '12345' },
});
}

11.2 Analytics SDK 架构

Analytics SDK(如 Google Analytics、Mixpanel)的核心是事件采集 + 数据上报

模块职责关键技术
采集器自动采集 PV、点击、滚动等MutationObserver、IntersectionObserver
会话管理用户标识、会话超时Cookie、localStorage、指纹
数据管道缓冲、批量、压缩、采样requestIdleCallback、sendBeacon
身份解析匿名 ID → 登录 ID 关联identify/alias

11.3 播放器 SDK 架构

播放器 SDK(如 video.js、DPlayer)是插件化架构的经典案例:

播放器 SDK 的特殊设计点:

  • 状态机:播放器有复杂的状态转换(idle → loading → playing → paused → ended)
  • 中间件模式:请求拦截(token 注入、CDN 切换)、响应拦截(DRM 解密)
  • 自适应码率:根据网速动态切换分辨率(ABR 算法)
  • 多端适配:H5、iOS WebView、Android WebView 的差异处理

十二、扩展设计

12.1 中间件模式

除了插件系统,SDK 还可以引入中间件模式处理数据流:

core/middleware.ts
type Middleware<T> = (data: T, next: () => Promise<T>) => Promise<T>;

class MiddlewareChain<T> {
private middlewares: Middleware<T>[] = [];

use(middleware: Middleware<T>): void {
this.middlewares.push(middleware);
}

async execute(data: T): Promise<T> {
let index = 0;

const next = async (): Promise<T> => {
if (index >= this.middlewares.length) {
return data;
}

const middleware = this.middlewares[index++];
return middleware(data, next); // 洋葱模型
};

return next();
}
}

// 使用示例:上报数据的中间件链
const reportChain = new MiddlewareChain<ReportData>();

// 中间件 1:添加公共字段
reportChain.use(async (data, next) => {
data.sdkVersion = '1.0.0';
data.timestamp = Date.now();
return next();
});

// 中间件 2:数据脱敏
reportChain.use(async (data, next) => {
if (data.userInfo?.phone) {
data.userInfo.phone = data.userInfo.phone.replace(
/(\d{3})\d{4}(\d{4})/,
'$1****$2'
);
}
return next();
});

// 中间件 3:采样控制
reportChain.use(async (data, next) => {
if (Math.random() > 0.1) return data; // 90% 丢弃
return next();
});

12.2 Hook 机制

提供生命周期 Hook,让用户在关键节点注入自定义逻辑:

core/hooks.ts
interface SDKHooks<TConfig extends BaseConfig> {
/** 数据上报前,可修改或拦截 */
beforeSend?: (data: ReportData) => ReportData | null;
/** 错误捕获后 */
onError?: (error: Error, info: ErrorInfo) => void;
/** 配置变更后 */
onConfigChange?: (config: TConfig) => void;
/** SDK 初始化完成 */
onReady?: () => void;
}

// 使用示例
const sdk = new SDK<MonitorConfig>({
name: 'monitor',
version: '1.0.0',
config: {
appId: 'xxx',
reportUrl: '/api/report',
hooks: {
beforeSend(data) {
// 过滤掉 Script error
if (data.message === 'Script error.') return null;
// 添加自定义字段
data.extra = { page: location.pathname };
return data;
},
onError(error) {
console.log('Custom error handler:', error.message);
},
},
},
});

常见面试问题

Q1: 如何设计一个可扩展的前端 SDK?

答案

设计可扩展 SDK 的核心是内核 + 插件架构,遵循「开放-封闭原则」:对扩展开放,对修改封闭。

设计要点

  1. 分层架构:用户层(API)→ 核心层(调度)→ 插件层(功能)→ 适配层(平台)
  2. 最小化内核:内核只负责生命周期管理、事件调度、配置管理、错误处理
  3. 插件系统:所有业务功能以插件形式实现,支持注册、卸载、依赖排序
  4. 事件总线:模块间通过事件通信,避免直接依赖
  5. TypeScript 类型:泛型配置、声明合并支持插件类型扩展
最小可用示例
// 内核只有几十行代码
const sdk = new SDK({ name: 'mySDK', version: '1.0.0', config: {} });

// 所有功能由插件提供
sdk.use(ErrorPlugin);
sdk.use(PerformancePlugin);
sdk.use(ReporterPlugin);

await sdk.init();

面试加分点:提到 Sentry 的 Integration、Vite 的 Plugin、Webpack 的 Tapable 等实际参考,并能对比它们的设计差异。

Q2: SDK 的插件系统怎么实现?

答案

插件系统实现的核心包括四个部分:

部分关键实现
插件接口定义 meta(名称、版本、依赖)+ install + uninstall
插件管理器注册、去重、拓扑排序、依次安装、逆序卸载
插件上下文暴露受限的 SDK 能力(配置、事件、日志)给插件
错误隔离单个插件安装失败不影响其他插件和内核

依赖排序使用拓扑排序算法:

// 插件声明依赖
const ReporterPlugin = {
meta: { name: 'reporter', dependencies: ['performance'] },
install(ctx) { /* ... */ },
};

// PluginManager 使用拓扑排序确保 performance 在 reporter 之前安装
// 同时检测循环依赖:visiting Set 中出现重复即为循环

**插件上下文(PluginContext)**是安全性的关键 —— 不直接暴露 SDK 实例,而是提供一个受限的接口,防止插件做破坏性操作。

Q3: 如何保证 SDK 的稳定性?

答案

SDK 的稳定性需要从设计开发测试运行时四个层面保障:

层面手段
设计插件错误隔离、全局错误边界、降级策略
开发严格的 TypeScript 类型、Code Review、100% 核心路径测试覆盖
测试单元测试、集成测试、兼容性测试(BrowserStack)、压力测试
运行时错误风暴保护(maxErrors)、异常不上抛到宿主、sendBeacon 保证数据

关键代码示例 —— 安全执行包装器

// 所有可能出错的操作都用 safeExecute 包裹
errorBoundary.safeExecute(
() => plugin.install(ctx),
{ phase: 'init', plugin: plugin.meta.name },
undefined // fallback 值
);

面试加分点

  • 提到 灰度发布:新版本先推 1% 用户,观察监控数据再全量
  • 提到 版本锁定:CDN 引入使用确定版本号 @1.2.3,而非 @latest
  • 提到 兼容性矩阵:维护一张 浏览器 x 特性 的支持表

Q4: SDK 的体积如何优化到极致?

答案

SDK 体积优化分为构建时运行时两个阶段:

构建时优化

策略效果实现方式
Tree Shaking移除未使用代码ESM 导出 + sideEffects: false
代码压缩减小体积 30%+Terser / esbuild minify
插件分包按需加载每个插件独立入口
零依赖避免引入第三方库自研轻量工具函数

运行时优化

// 动态导入:只在需要时加载插件
const sdk = new SDK({ name: 'app', version: '1.0.0', config: {} });

// Core 只有 2KB,插件按需加载
if (needPerformanceMonitoring) {
const { PerformancePlugin } = await import('@my-org/sdk/plugins/performance');
sdk.use(PerformancePlugin);
}

体积审计工具

  • bundlephobia.com:查看 npm 包体积
  • webpack-bundle-analyzer:可视化分析打包产物
  • size-limit:CI 中自动检查体积是否超标
package.json
{
"size-limit": [
{ "path": "dist/index.esm.js", "limit": "3 KB" },
{ "path": "dist/plugins/performance.esm.js", "limit": "2 KB" }
]
}

Q5: 如何实现 SDK 的多端适配(浏览器、Node.js、小程序)?

答案

多端适配的核心是平台抽象层(Platform Adapter)

platform/adapter.ts
interface PlatformAdapter {
/** 网络请求 */
request(url: string, options: RequestOptions): Promise<Response>;
/** 存储 */
getStorage(key: string): string | null;
setStorage(key: string, value: string): void;
/** 定时器 */
setTimeout(fn: () => void, ms: number): number;
/** 页面可见性 */
onVisibilityChange?(callback: (visible: boolean) => void): void;
}

// 浏览器适配
class BrowserAdapter implements PlatformAdapter {
request(url: string, options: RequestOptions) {
return fetch(url, options);
}
getStorage(key: string) {
return localStorage.getItem(key);
}
// ...
}

// Node.js 适配
class NodeAdapter implements PlatformAdapter {
async request(url: string, options: RequestOptions) {
const { default: fetch } = await import('node-fetch');
return fetch(url, options);
}
getStorage() {
return null; // Node.js 无 localStorage
}
// ...
}

SDK 初始化时自动检测环境并选择适配器,也支持用户手动注入:

const sdk = new SDK({
name: 'mySDK',
version: '1.0.0',
config: {},
// 自动检测或手动指定
platform: isBrowser() ? new BrowserAdapter() : new NodeAdapter(),
});

相关链接