New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

mini-di-cocos

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mini-di-cocos

Lightweight DI container for Cocos Creator 3.x - inspired by VContainer and Zenject, optimized for Playable Ads

latest
npmnpm
Version
1.0.0
Version published
Weekly downloads
9
-50%
Maintainers
1
Weekly downloads
 
Created
Source

MiniDI

Lightweight Dependency Injection container for Cocos Creator 3.x, inspired by VContainer and Zenject.

Optimized for Playable Ads with minimal bundle size impact.

Features

  • 🚀 No reflect-metadata — uses static properties, works with any Cocos build
  • 🎮 Cocos Creator native — seamless integration with Components and Prefabs
  • 📦 Minimal footprint — optimized for playable ads (2-15MB limits)
  • 🔄 Lifecycle hooks — IInitializable, IStartable, ITickable, IDisposable
  • 🏭 VContainer-style instantiatecontainer.instantiate(prefab) with auto-injection
  • 🔍 Zenject-style scene injection — automatic injection across entire scene

Installation

npm install mini-di-cocos --legacy-peer-deps

Or add to your .npmrc:

legacy-peer-deps=true

Quick Start

1. Create Services

import { Injectable, IInitializable, IDisposable } from 'mini-di-cocos';

// Simple service
@Injectable([])
class ConfigService {
    readonly maxScore = 1000;
    readonly soundEnabled = true;
}

// Service with dependencies
@Injectable([ConfigService])
class AudioService implements IInitializable, IDisposable {
    constructor(private config: ConfigService) {}

    initialize(): void {
        console.log('AudioService initialized');
    }

    playSound(name: string): void {
        if (this.config.soundEnabled) {
            // play sound
        }
    }

    dispose(): void {
        console.log('AudioService disposed');
    }
}

@Injectable([AudioService, ConfigService])
class GameService {
    constructor(
        private audio: AudioService,
        private config: ConfigService
    ) {}

    onScore(): void {
        this.audio.playSound('score');
    }
}

2. Create LifetimeScope

import { _decorator } from 'cc';
import { LifetimeScope, ContainerBuilder } from 'mini-di-cocos';

const { ccclass } = _decorator;

@ccclass('GameScope')
export class GameScope extends LifetimeScope {
    protected configure(builder: ContainerBuilder): void {
        builder.register(ConfigService);
        builder.register(AudioService);
        builder.register(GameService);
    }
}

3. Inject into Cocos Components

import { _decorator, Component } from 'cc';
import { NeedInject, InjectProperty } from 'mini-di-cocos';

@NeedInject()
@ccclass('PlayerController')
export class PlayerController extends Component {
    @InjectProperty(GameService)
    private gameService!: GameService;

    @InjectProperty(AudioService)
    private audio!: AudioService;

    onCollectCoin(): void {
        this.gameService.onScore();
        this.audio.playSound('coin');
    }
}

4. Scene Setup

Scene Hierarchy:
├── GameScope (LifetimeScope)    ← Add your scope here
├── Player
│   └── PlayerController         ← @NeedInject components
├── UI
│   └── ScoreView                ← @NeedInject components
└── ...

API Reference

Decorators

@Injectable(dependencies: Token[])

Marks a class for constructor injection.

@Injectable([DepA, DepB])
class MyService {
    constructor(private depA: DepA, private depB: DepB) {}
}

@NeedInject()

Marks a Cocos Component for property injection.

@NeedInject()
@ccclass('MyComponent')
class MyComponent extends Component {}

@InjectProperty(token: Token)

Injects a dependency into a property.

@InjectProperty(AudioService)
private audio!: AudioService;

Container

// Registration
container.register(Token, options?)
container.registerSingleton(Token, useClass?)
container.registerTransient(Token, useClass?)
container.registerInstance(Token, instance)
container.registerFactory(Token, factory, lifetime?)

// Resolution
container.resolve<T>(Token): T
container.tryResolve<T>(Token): T | null
container.isRegistered(Token): boolean

// Cocos Integration
container.injectComponent(component)
container.injectNode(node)
container.instantiate(prefab, parent?): Node
container.instantiateAndGet<T>(prefab, type, parent?): T

// Lifecycle
container.initializeAll()
container.startAll()
container.disposeAll()
container.getAllSingletons(): any[]

ContainerBuilder

const builder = new ContainerBuilder();

builder
    .register(ServiceA)
    .register(ServiceB, { useClass: ServiceBImpl })
    .registerSingleton(ServiceC)
    .registerTransient(ServiceD)
    .registerInstance(Config, configInstance)
    .registerFactory(ServiceE, (c) => new ServiceE(c.resolve(ServiceA)));

const container = builder.build();

LifetimeScope

PropertyDefaultDescription
autoInjectScenetrueAuto-inject all @NeedInject components in scene
autoInitializetrueAuto-call IInitializable.initialize()
autoStarttrueAuto-call IStartable.start()
enableTickManagerfalseEnable ITickable support
@ccclass('GameScope')
class GameScope extends LifetimeScope {
    protected configure(builder: ContainerBuilder): void {
        // Register your services here
    }
}

// Static access
const scope = LifetimeScope.findInScene();
const service = scope?.resolve(MyService);

InjectionToken

For interface-based injection (TypeScript interfaces are erased at runtime):

interface IAudioService {
    playSound(name: string): void;
}

const IAudioService = new InjectionToken<IAudioService>('IAudioService');

// Registration
builder.register(IAudioService, {
    useFactory: (c) => new AudioServiceImpl(c.resolve(ConfigService))
});

// Injection
@InjectProperty(IAudioService)
private audio!: IAudioService;

Lifecycle Interfaces

interface IInitializable {
    initialize(): void | Promise<void>;
}

interface IStartable {
    start(): void;
}

interface ITickable {
    tick(deltaTime: number): void;
}

interface IFixedTickable {
    fixedTick(fixedDeltaTime: number): void;
}

interface ILateTickable {
    lateTick(deltaTime: number): void;
}

interface IDisposable {
    dispose(): void;
}

Execution Order:

onLoad()
  └── Container.build()
  └── IInitializable.initialize() (registration order)
  └── Scene injection

start()
  └── IStartable.start() (registration order)

update(dt)
  └── ITickable.tick(dt)
  └── IFixedTickable.fixedTick(fixedDt) (fixed timestep)

lateUpdate(dt)
  └── ILateTickable.lateTick(dt)

onDestroy()
  └── IDisposable.dispose() (reverse order)

Dynamic Instantiation

@Injectable([Container])
class EnemySpawner {
    @property(Prefab)
    private enemyPrefab!: Prefab;

    constructor(private container: Container) {}

    spawn(parent: Node): Enemy {
        // Instantiate with auto-injection
        return this.container.instantiateAndGet(
            this.enemyPrefab,
            Enemy,
            parent
        );
    }
}

Lifetime

TypeDescription
Lifetime.SingletonOne instance per container (default)
Lifetime.TransientNew instance on each resolve

Comparison

FeatureMiniDIVContainerZenject
PlatformCocos CreatorUnityUnity
reflect-metadata❌ Not required✅ Required✅ Required
Bundle size~5KB~50KB~200KB
Constructor injection
Property injection
Scene injection
Prefab instantiate
Lifecycle hooks
Scoped containers

Best Practices

1. Service vs Component

// ✅ Services — constructor injection
@Injectable([ConfigService])
class AudioService {
    constructor(private config: ConfigService) {}
}

// ✅ Cocos Components — property injection
@NeedInject()
@ccclass('PlayerController')
class PlayerController extends Component {
    @InjectProperty(AudioService)
    private audio!: AudioService;
}

2. Interface Segregation

// Define interfaces
interface IAudioService {
    playSound(name: string): void;
}

// Create tokens
const IAudioService = new InjectionToken<IAudioService>('IAudioService');

// Register implementation
builder.register(IAudioService, { useClass: AudioServiceImpl });

3. Factory Registration

builder.registerFactory(ComplexService, (container) => {
    const config = container.resolve(ConfigService);
    const audio = container.resolve(AudioService);
    
    return new ComplexService(config, audio, {
        debug: true,
        maxRetries: 3
    });
});

Troubleshooting

"X is not registered"

Ensure the dependency is registered before resolving:

protected configure(builder: ContainerBuilder): void {
    builder.register(DependencyFirst);  // Register dependencies first
    builder.register(ServiceThatNeedsIt);
}

"Circular dependency detected"

Break the cycle with factory or lazy resolution:

@Injectable([])
class ServiceA {
    private _serviceB: ServiceB | null = null;
    
    setServiceB(b: ServiceB): void {
        this._serviceB = b;
    }
}

// In configure:
builder.registerFactory(ServiceA, (c) => {
    const a = new ServiceA();
    const b = c.resolve(ServiceB);
    a.setServiceB(b);
    return a;
});

Component not injected

  • Check @NeedInject() decorator is present
  • Check component is in scene when LifetimeScope loads
  • For dynamic objects, use container.instantiate()

License

MIT

Keywords

cocos

FAQs

Package last updated on 26 Nov 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts