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

@libeilong/lazy-singleton

v0.1.0

Published

一个零依赖、仅 ~500 字节的微型工具库,用于创建延迟初始化的单例。它可以从根本上解决 JavaScript/TypeScript 项目中的循环依赖问题,并提供一流的开发体验,包括对 Vite 和 Webpack 的状态保留热重载(HMR)支持。

Readme

lazy-singleton

一个零依赖、仅 ~500 字节的微型工具库,用于创建延迟初始化的单例。它可以从根本上解决 JavaScript/TypeScript 项目中的循环依赖问题,并提供一流的开发体验,包括对 Vite 和 Webpack 的状态保留热重载(HMR)支持。

😩 您是否曾陷入这样的困境?

在大型项目中,模块间的依赖关系变得复杂:stores 依赖 api,api 依赖 request,而 request 又需要在出错时调用 stores 的方法。

// stores/index.ts
import { api } from '../api'
// ...

// api/index.ts
import { request } from '../request'
// ...

// request.ts
import { stores } from '../stores' // <-- 循环依赖!

这会导致在模块加载时,其中一个模块拿到的是 undefined,最终在浏览器中抛出致命错误:Uncaught TypeError: Cannot read properties of undefined

lazy-singleton 通过将模块的初始化推迟到第一次实际使用时,优雅地打破了这个死结。

✨ 核心特性

🌀 轻松解决循环依赖:通过延迟加载,从根本上消除模块加载时的初始化冲突。

⚡️ 延迟初始化 (Lazy):只在第一次访问实例时才执行初始化代码,可以减少应用的启动时间。

📦 单例保证:确保在应用的整个生命周期中,一个实例只被创建一次。

🔥 状态保留热重载 (HMR):可选地在 Vite 和 Webpack 的热更新之间保留实例状态,提供极致的开发体验。

🌐 环境无关:可在 Node.js、Vite、Webpack 和浏览器中无缝工作。

💪 健壮透明:基于原生 Proxy 实现,自动处理 this 绑定,instanceof 和 Object.keys 等操作均符合预期。

📦 零依赖,体积微小:干净、轻量,可无负担地添加到任何项目中。

🚀 安装

npm install lazy-singleton
# or
yarn add lazy-singleton
# or
pnpm add lazy-singleton

💡 基本用法

只需用 lazy() 函数包裹您的实例创建逻辑即可。

之前 (有循环依赖风险):

// logger.ts
class LoggerService {
  constructor() {
    console.log('Logger initialized!')
  }
  log(message: string) {
    /*...*/
  }
}
export const logger = new LoggerService()

之后 (安全且延迟加载):

// logger.ts
import { lazy } from 'lazy-singleton'

class LoggerService {
  constructor() {
    // 这段代码只会在第一次调用 `logger.log()` 时执行
    console.log('Logger initialized!')
  }
  log(message: string) {
    /*...*/
  }
}

export const logger = lazy(() => new LoggerService())

现在,即使其他模块与 logger.ts 存在循环依赖,应用也能正常工作。

🛠️ 高级 API

lazy 函数接受一个可选的 options 对象,用于开启高级功能。

lazy<T>(initializer: () => T, options?: Options): T

interface Options {
  /**
   * 用于 HMR 状态保留的唯一键。
   * 如果提供,将在 Vite/Webpack 热更新之间尝试保留实例。
   */
  hmrKey?: string;

  /**
   * 是否在控制台打印初始化和 HMR 日志,便于调试。
   */
  debug?: boolean;
}

🔥 状态保留热重载 (HMR)

这是 lazy-singleton 的王牌功能。在前端开发中,我们希望修改代码后,应用的状态(如用户登录信息)能够被保留。

使用 hmrKey 来开启此功能:

// src/stores/index.ts
import { lazy } from 'lazy-singleton'
import { UserStore } from './userStore'
import { AppStore } from './appStore'

export const stores = lazy(
  () => {
    return {
      user: new UserStore(),
      app: new AppStore(),
    }
  },
  {
    // 提供一个唯一的 key
    hmrKey: 'stores',
    // 开启调试日志
    debug: true,
  },
)

现在,当您修改 UserStore.ts 或任何相关文件时:

  • Vite/Webpack 会进行热模块替换。
  • lazy-singleton 会自动保存旧模块的 stores 实例,并在新模块加载后恢复它。
  • 您在控制台会看到 [lazy:stores] ✅ Instance restored from HMR. 的日志。
  • 您的应用状态得以保留,无需重新登录或刷新页面!

最佳实践:仅为需要保留状态的模块(如 stores)开启 hmrKey。对于无状态的模块(如 api 客户端、router 实例),不提供 hmrKey,让它们在热更新时重新创建以确保代码总是最新的。

🎯 场景示例

1. 前端应用 (Vite/React)

完美解决 stores, api, router 之间的复杂依赖关系。

// src/stores/index.ts
// 状态管理中心,需要保留 HMR 状态
export const stores = lazy(() => ({
  user: new UserStore(),
  app: new AppStore(),
}), { hmrKey: 'global-stores' });


// src/api/index.ts
import * as user from './user';
// API 客户端,无状态,总是希望使用最新的代码
export const API = lazy(() => ({
  user,
  // ...
}));


// src/router.ts
// 路由实例,无状态
export const router = lazy(() => createBrowserRouter(...));

2. 后端应用 (Node.js/Express)

在 Node.js 中,它同样可以解决服务间的循环依赖,并能延迟初始化昂贵的资源,如数据库连接。

// src/services.ts
import { lazy } from 'lazy-singleton'
import { UserService } from './user.service'
import { AuthService } from './auth.service'

// AuthService 和 UserService 互相依赖
export const services = lazy(() => ({
  user: new UserService(),
  auth: new AuthService(),
}))

// src/database.ts
import { createPool } from 'mysql2/promise'

// 数据库连接池只在第一次数据库操作时才被创建
export const db = lazy(() => {
  console.log('Creating database connection pool...')
  return createPool({
    /* config */
  })
})

🔧 它是如何工作的?

lazy-singleton 返回一个原生 JavaScript Proxy 对象。这个代理对象在内部持有一个尚未初始化的实例。当您第一次尝试访问代理对象的任何属性时(例如 stores.user),Proxy 会拦截该访问,执行您传入的 initializer 函数来创建原始实例,将其缓存,然后再将访问请求转发给这个真实的实例。后续的所有访问都将直接使用缓存的实例。

HMR 状态保留功能是通过 Vite (import.meta.hot) 和 Webpack (module.hot) 提供的 HMR API 实现的。