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

@3lens/angular-bridge

v1.0.1

Published

Angular integration for 3Lens - the three.js devtool

Downloads

141

Readme

@3lens/angular-bridge

Angular integration for 3Lens - the three.js devtool.

Installation

npm install @3lens/angular-bridge @3lens/core @3lens/overlay
# or
pnpm add @3lens/angular-bridge @3lens/core @3lens/overlay

Quick Start

Module-based Setup

import { NgModule } from '@angular/core';
import { ThreeLensModule } from '@3lens/angular-bridge';

@NgModule({
  imports: [
    ThreeLensModule.forRoot({
      appName: 'My Angular Three.js App',
      debug: false,
      showOverlay: true,
    }),
  ],
})
export class AppModule {}

Standalone Components Setup

import { bootstrapApplication } from '@angular/platform-browser';
import { provideThreeLens } from '@3lens/angular-bridge';

bootstrapApplication(AppComponent, {
  providers: [
    provideThreeLens({ appName: 'My App' }),
  ],
});

Using the Service

import { Component, OnInit, OnDestroy } from '@angular/core';
import { ThreeLensService } from '@3lens/angular-bridge';
import * as THREE from 'three';

@Component({
  selector: 'app-scene',
  template: `
    <div class="stats">
      <div>FPS: {{ fps$ | async | number:'1.0-0' }}</div>
      <div>Draw Calls: {{ drawCalls$ | async }}</div>
      <div>Triangles: {{ triangles$ | async | number }}</div>
    </div>
    <canvas #canvas></canvas>
  `,
})
export class SceneComponent implements OnInit, OnDestroy {
  fps$ = this.threeLens.fps$;
  drawCalls$ = this.threeLens.drawCalls$;
  triangles$ = this.threeLens.triangles$;

  private renderer!: THREE.WebGLRenderer;
  private scene!: THREE.Scene;
  private camera!: THREE.PerspectiveCamera;

  constructor(private threeLens: ThreeLensService) {}

  ngOnInit(): void {
    // Set up Three.js
    this.renderer = new THREE.WebGLRenderer();
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000);

    // Connect 3Lens
    this.threeLens.observeRenderer(this.renderer);
    this.threeLens.observeScene(this.scene);

    // Optional: Initialize helpers
    this.threeLens.initializeTransformGizmo(
      this.scene,
      this.camera,
      this.renderer.domElement,
      THREE
    );
  }

  ngOnDestroy(): void {
    this.renderer.dispose();
  }
}

Service API

Observables

// Frame stats (updates every frame)
frameStats$: Observable<FrameStats | null>

// Convenience observables
fps$: Observable<number>
drawCalls$: Observable<number>
triangles$: Observable<number>
frameTime$: Observable<number>
gpuMemory$: Observable<number>

// Scene state
snapshot$: Observable<SceneSnapshot | null>
selectedNode$: Observable<SceneNode | null>
isReady$: Observable<boolean>
isOverlayVisible$: Observable<boolean>

Methods

// Scene observation
observeRenderer(renderer: THREE.WebGLRenderer): void
observeScene(scene: THREE.Scene): void

// Snapshots
takeSnapshot(): SceneSnapshot | null

// Selection
selectObject(uuid: string): void
clearSelection(): void
focusOnSelected(): void
flyToSelected(): void

// Entity registration
registerEntity(object: THREE.Object3D, options?: EntityOptions): void
unregisterEntity(object: THREE.Object3D): void

// Overlay control
showOverlay(): void
hideOverlay(): void
toggleOverlay(): void

Entity Registration

Register Three.js objects with meaningful names and metadata:

@Component({...})
export class PlayerComponent implements AfterViewInit {
  private playerMesh!: THREE.Mesh;

  constructor(private threeLens: ThreeLensService) {}

  ngAfterViewInit(): void {
    this.playerMesh = this.createPlayerMesh();

    this.threeLens.registerEntity(this.playerMesh, {
      name: 'Player',
      module: 'game/entities',
      metadata: {
        health: 100,
        level: 5,
        class: 'Warrior',
      },
      tags: ['player', 'controllable', 'saveable'],
    });
  }

  ngOnDestroy(): void {
    this.threeLens.unregisterEntity(this.playerMesh);
  }
}

Directive Usage

For declarative entity registration:

import { ThreeLensEntityDirective } from '@3lens/angular-bridge';

@Component({
  selector: 'app-enemy',
  standalone: true,
  imports: [ThreeLensEntityDirective],
  template: `<ng-content></ng-content>`,
})
export class EnemyComponent implements AfterViewInit {
  @Input() enemyData: any;

  mesh!: THREE.Mesh;

  // Use programmatically with the directive's inputs
  entityName = 'Enemy';
  entityModule = 'game/enemies';
  entityMetadata = { type: 'goblin' };
}

Nx Workspace Integration

For Nx monorepos, use the NxLibraryHelper to maintain consistent naming:

import { Injectable } from '@angular/core';
import { ThreeLensService, NxLibraryHelper } from '@3lens/angular-bridge';

@Injectable({ providedIn: 'root' })
export class TerrainService {
  private nxHelper: NxLibraryHelper;

  constructor(private threeLens: ThreeLensService) {
    this.nxHelper = new NxLibraryHelper(threeLens, {
      name: 'feature-terrain',
      scope: 'world',
      type: 'feature',
    });
  }

  createTerrain(): THREE.Mesh {
    const terrain = new THREE.Mesh(/* ... */);

    // Registers as @world/feature-terrain/MainTerrain
    this.nxHelper.registerEntity(terrain, 'MainTerrain', {
      metadata: { size: 1000, resolution: 256 },
    });

    return terrain;
  }
}

Scoped Registration

For components with multiple parts:

const registerPart = this.nxHelper.createScopedRegistrar('Vehicle');

// Registers as @game/feature-vehicles/Vehicle/Body
registerPart(bodyMesh, 'Body');

// Registers as @game/feature-vehicles/Vehicle/Wheel_FL
registerPart(wheelFLMesh, 'Wheel_FL');

Injection Tokens

For advanced use cases, inject the probe directly:

import { Inject } from '@angular/core';
import { THREELENS_PROBE, DevtoolProbe } from '@3lens/angular-bridge';

@Component({...})
export class AdvancedComponent {
  constructor(@Inject(THREELENS_PROBE) private probe: DevtoolProbe) {
    // Direct probe access
    const stats = probe.getLatestFrameStats();
  }
}

TypeScript

All exports are fully typed:

import type {
  ThreeLensModuleConfig,
  EntityOptions,
  NxLibraryOptions,
  FrameStats,
  SceneSnapshot,
  SceneNode,
} from '@3lens/angular-bridge';

License

MIT