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

entt-js

v0.1.2

Published

TypeScript port of EnTT - a fast and reliable entity-component-system (ECS) implementation.

Readme

entt-js

npm version License: MIT

Sponsors

Overview

A TypeScript port of EnTT - a fast and reliable Entity Component System (ECS) implementation.

entt-js brings the power of EnTT's battle-tested ECS architecture to TypeScript/JavaScript, offering a high-performance solution for entity management and component-based game development. This library maintains the core design philosophy of the original C++ implementation while leveraging TypeScript's type system for enhanced developer experience.

What is ECS?

Entity Component System (ECS) is an architectural pattern commonly used in game development that separates data (Components) from entities (Entities) and logic (Systems). This approach provides:

  • High Performance: Cache-friendly data layouts and efficient iteration
  • Flexibility: Easy composition of game objects from reusable components
  • Scalability: Handles thousands to millions of entities efficiently
  • Maintainability: Clear separation of concerns

Features

  • 🚀 High Performance: Optimized sparse set implementation with cache-friendly memory layouts
  • 📦 Full TypeScript Support: Comprehensive type definitions with advanced type inference
  • 🎯 Type-Safe API: Leverages TypeScript's type system to catch errors at compile time
  • 🔄 Entity Lifecycle: Complete entity creation, destruction, and recycling
  • 🧩 Component Management: Add, remove, and query components with ease
  • 👁️ Views & Groups: Efficient iteration over entities with specific components
  • 📡 Signals: Event system for component lifecycle hooks
  • 🔍 Runtime Views: Dynamic component queries without compile-time types
  • 📸 Snapshots: Save and restore registry state
  • 🌐 Multi-Platform: Works in Node.js and browsers

Installation

npm install entt-js

Quick Start

import { Registry } from 'entt-js'

// Define your components
class Position {
  x: number
  y: number
  constructor(x = 0, y = 0) {
    this.x = x
    this.y = y
  }
}

class Velocity {
  dx: number
  dy: number
  constructor(dx = 0, dy = 0) {
    this.dx = dx
    this.dy = dy
  }
}

// Create a registry
const registry = new Registry()

// Create entities and attach components
const entity = registry.create()
registry.emplace(entity, Position, 10, 20)
registry.emplace(entity, Velocity, 1, 0)

// Query entities with specific components
const view = registry.view([Position, Velocity])
view.each((entity, position, velocity) => {
  position.x += velocity.dx
  position.y += velocity.dy
})

// for-of iteration
for (const [entity, position, velocity] of view.each()) {
  position.x += velocity.dx
  position.y += velocity.dy
}

Browser Support

The library supports both Node.js and modern browsers. For browser usage:

No Bundler

<!-- IIFE -->
<script src="https://cdn.jsdelivr.net/npm/entt-js/dist/browser/entt.min.js"></script>

<script>
const { Registry } = window.entt
</script>

or

<!-- ESM -->
<script type="module">
import { Registry } from 'https://cdn.jsdelivr.net/npm/entt-js/dist/browser/index.min.js'
</script>

With Bundler

import { Registry } from 'entt-js'

Core Concepts

Registry

The Registry is the central hub for managing entities and components:

const registry = new Registry()

// Create entities
const entity1 = registry.create()
const entity2 = registry.create()

// Check if entity is valid
registry.valid(entity1) // true

// Destroy entity
registry.destroy(entity1)

Components

Components are plain TypeScript classes or objects that hold data:

class Health {
  value: number
  constructor(value = 0) {
    this.value = value
  }
}

class Transform {
  x: number
  y: number
  rotation: number

  constructor(x = 0, y = 0, rotation = 0) {
    this.x = x
    this.y = y
    this.rotation = rotation
  }
}

// Attach components to entities
registry.emplace(entity, Health, 100)
registry.emplace(entity, Transform, 10, 20, 0)

// Retrieve components
const health = registry.get(entity, Health)
const transform = registry.get(entity, Transform)

// Check component existence
registry.allOf(entity, Health, Transform) // true
registry.anyOf(entity, Health) // true

// Remove components
registry.remove(entity, Health)

Views

Views provide efficient iteration over entities with specific component sets:

// Create a view for entities with Position and Velocity
const view = registry.view([Position, Velocity])

// Iterate with entity and components
view.each((entity, position, velocity) => {
  position.x += velocity.dx
  position.y += velocity.dy
})

// Iterate with components only
view.each((position, velocity) => {
  console.log(`Position: (${position.x}, ${position.y})`)
}, true)

// Exclude certain components
const viewWithExclusion = registry.view([Position], [Velocity])

Groups

Groups offer even better performance for frequently accessed component combinations:

// Owning group - optimizes storage layout
const group = registry.group([Position, Velocity])

group.each((entity, position, velocity) => {
  // High-performance iteration
  position.x += velocity.dx
  position.y += velocity.dy
})

// Non-owning group with additional components
const complexGroup = registry.group([], [Position, Velocity, Health])

Signals

React to component lifecycle events:

// Listen for component creation
registry.onConstruct(Position).connect((registry, entity) => {
  console.log(`Position component added to entity ${entity}`)
})

// Listen for component updates
registry.onUpdate(Health).connect((registry, entity) => {
  const health = registry.get(entity, Health)
  console.log(`Health updated to ${health.value}`)
})

// Listen for component destruction
registry.onDestroy(Position).connect((registry, entity) => {
  console.log(`Position component removed from entity ${entity}`)
})

Sorting

Sort entities based on component values:

// Sort by component property
registry.sort(Position, (a, b) => a.x - b.x)

// Sort by entity relationship
registry.sortByEntity(Position, (e1, e2) => e1 - e2)

// Sort to match another component's order
registry.sortAs(Velocity, Position)

Advanced Features

Runtime Views

When component types aren't known at compile time:

import { RuntimeView } from 'entt-js'

const runtimeView = new RuntimeView()

const UnknownType1 = /* ? */
const UnknownType2 = /* ? */
runtimeView.iterate(registry.getStorage(UnknownType1))
runtimeView.iterate(registry.getStorage(UnknownType2))

for (const entity of runtimeView) {
  // Process entities
}

Snapshots

Save and restore registry state:

import { Snapshot, SnapshotLoader, Registry } from 'entt-js'

const registry = new Registry()
const snapshot = new Snapshot(registry)
const output = {
  saveSize(size) { /* ... */ }
  saveEntity(entity) { /* ... */ }
  saveComponent(component) { /* ... */ }
}
snapshot
  .get(output, Position)
  .get(output, Velocity)

// Load snapshot into another registry
const newRegistry = new Registry()
const loader = new SnapshotLoader(newRegistry)
const input = {
  loadSize(ref) { ref.set(/* size */) }
  loadEntity(ref) { ref.set(/* entity */) }
  loadComponent(ref) {
    const defaultComponent = ref.get()
    ref.set(/* ... */)
  }
}
loader
  .get(input, Position)
  .get(input, Velocity)

Custom Entity Types

Use custom entity identifiers:

import { basicRegistryTemplate } from 'entt-js'

// Use BigInt entities for larger capacity
const BigIntRegistry = basicRegistryTemplate.instantiate(BigInt)
const bigintRegistry = new BigIntRegistry()

// Custom entity class
class EntityObject {
  // static member `EntityType` is required
  static EntityType = Number

  version: number
  value: number

  constructor(value = 0) {
    this.version = ((value >>> 20) & 0xFFF) >>> 0
    this.value = (value & 0xFFFFF) >>> 0
  }

  // required for internal implicit convertion
  [Symbol.toPrimitive]() {
    return ((this.value & 0xFFFFF)
      | ((this.version & 0xFFF) << 20)) >>> 0
  }
}

const EntityObjectRegistry = basicRegistryTemplate.instantiate(EntityObject)
const entityObjectRegistry = new EntityObjectRegistry()

Config Flags

The library supports several compile-time configuration flags to customize behavior and optimize performance:

Available Flags

| Flag | Type | Default | Description | |------|------|---------|-------------| | ENTT_SPARSE_PAGE | number | 4096 | Size of sparse array pages (affects memory layout) | | ENTT_PACKED_PAGE | number | 1024 | Size of packed array pages (affects memory layout) | | ENTT_NO_ETO | boolean | false | Disable Empty Type Optimization (ETO) | | ENTT_NO_MIXIN | boolean | false | Disable signal mixin functionality |

Usage in Different Environments

Node.js (without bundler):

Define flags as global variables before importing the library:

global.ENTT_SPARSE_PAGE = 8192
global.ENTT_PACKED_PAGE = 2048
global.ENTT_NO_ETO = true
global.ENTT_NO_MIXIN = false

const { Registry } = require('entt-js')

Browser (no bundler):

Define flags on the window object before loading the script:

<script>
  window.ENTT_SPARSE_PAGE = 8192
  window.ENTT_PACKED_PAGE = 2048
  window.ENTT_NO_ETO = true
  window.ENTT_NO_MIXIN = false
</script>
<script src="https://cdn.jsdelivr.net/npm/entt-js/dist/browser/entt.min.js"></script>

With Bundler (Webpack, Vite, Rollup, etc.):

Use bundler's define plugin to set flags at build time:

// vite.config.js
export default {
  define: {
    ENTT_SPARSE_PAGE: 8192,
    ENTT_PACKED_PAGE: 2048,
    ENTT_NO_ETO: true,
    ENTT_NO_MIXIN: false
  }
}

// webpack.config.js
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      ENTT_SPARSE_PAGE: 8192,
      ENTT_PACKED_PAGE: 2048,
      ENTT_NO_ETO: true,
      ENTT_NO_MIXIN: false
    })
  ]
}

// rollup.config.js
import replace from '@rollup/plugin-replace'

export default {
  plugins: [
    replace({
      ENTT_SPARSE_PAGE: 8192,
      ENTT_PACKED_PAGE: 2048,
      ENTT_NO_ETO: true,
      ENTT_NO_MIXIN: false,
      preventAssignment: true
    })
  ]
}

Flag Details

ENTT_SPARSE_PAGE: Controls the page size for sparse arrays in the sparse set implementation. Larger values use more memory but may reduce allocations. Adjust based on your entity count and memory constraints.

ENTT_PACKED_PAGE: Controls the page size for packed component arrays. Larger values improve cache locality for component iteration but increase memory overhead.

ENTT_NO_ETO: When true, disables Empty Type Optimization. ETO allows empty components (tag components) to avoid memory allocation. Disable if you encounter issues with empty class detection.

ENTT_NO_MIXIN: When true, disables the signal mixin system. This removes lifecycle event support (onConstruct, onUpdate, onDestroy) but slightly reduces memory overhead.

Performance

The library is designed for high performance with:

  • Sparse Set Architecture: O(1) component access and iteration
  • Cache-Friendly Layouts: Contiguous memory for better CPU cache utilization
  • Efficient Iteration: Direct array access without indirection
  • Minimal Allocations: Object pooling and reuse where possible

Benchmark Results

Performance benchmarks with 1,000,000 entities on Node.js v24 (Apple M2):

| Operation | Time | Description | |-----------|------|-------------| | Create entities | 0.55s | Creating 1M entities | | Single component iteration | 0.013s | Iterating over 1M entities with 1 component | | Two components iteration | 0.40s | Iterating over 1M entities with 2 components | | Owning group iteration | 0.25s | Iterating 1M entities in full owning group (2 components) | | Component access (registry) | 0.12s | Getting component from registry for 1M entities | | Component access (view) | 0.11s | Getting component from view for 1M entities |

Note: These are raw JavaScript performance numbers. While not matching native C++ speeds, the library provides excellent performance for JS-based applications and games.

Real-World Performance

For practical game development scenarios:

  • Typical games process 1,000-50,000 entities per frame
  • At 60 FPS (16.67ms budget per frame):
    • Iterating 10,000 entities with 2 components: ~0.004ms
    • Iterating 50,000 entities with 2 components: ~0.02ms
  • Iteration overhead is negligible - rendering and game logic are typically the bottlenecks

Performance characteristics:

  • ✅ Component iteration speed rivals native array performance
  • ✅ Owning groups provide 1.6x speedup over regular views
  • ✅ Component access is near-optimal (~0.0001ms per operation)

Compared to alternatives:

  • More user-friendly than ArrayBuffer-based libraries (e.g., bitECS) while maintaining competitive performance
  • Superior type safety and developer experience compared to other JS ECS implementations
  • Excellent performance/usability balance for TypeScript projects

Run benchmarks yourself:

npm run benchmark

See tests/benchmark for detailed performance tests.

Development

Install dependencies:

Node.js v24+

npm install

Run tests:

npm run test

Run benchmarks:

npm run benchmark

Build the library:

npm run build

Type checking:

npm run typecheck

Differences from C++ EnTT

While this library aims to maintain API compatibility with the original EnTT, some differences exist due to TypeScript/JavaScript limitations:

  • No Template Specialization: Uses runtime type registration instead
  • Memory Management: Relies on JavaScript garbage collection
  • Performance: Generally slower than C++ but highly optimized for JS
  • Type Safety: Leverages TypeScript's type system for compile-time safety

Credits

This project is a TypeScript port of EnTT by @skypjack.

Related Projects

  • EnTT - The original C++ implementation
  • bitecs - Another ECS library for JavaScript

Links