< Back to all hacks

#19 Dead Packages (_DEAD_PKGS instant stubs)

RAM
Problem
Even lazy loading loads modules when accessed. Some providers will NEVER be used on this device.
Solution
Split into _DEAD_PKGS (instant stub, 0 MB) vs _LAZY_PKGS (defer until use). 23 dead, 6 deferred.
Lesson
Not all unused code is equal. Dead code that will never execute deserves a cheaper stub than code that might execute later. Two-tier interception (dead vs lazy) optimizes for both cases.

Context

The hijack.js module interception system already supports lazy loading via Proxy objects (hack 15) and ESM stubs at the file level (hack 18). Lazy loading defers module initialization until a property is first accessed, but the Proxy itself has overhead: it intercepts every property access, triggers the real require() on first use, and then keeps the fully loaded module in memory permanently.

For packages that will genuinely never be used on the Moto E2 -- cloud providers whose API keys are not configured, features not enabled, platforms that do not apply -- even a Proxy is wasteful. These packages can be replaced with an instant empty object stub that returns immediately, requiring zero filesystem I/O, zero JavaScript parsing, and zero heap allocation beyond the frozen empty object itself.

The solution splits the 37 interceptable packages into three tiers:

  • _DEAD_PKGS (23 packages): Return {} immediately. Zero cost. No Proxy, no deferred loading, no filesystem access. Confirmed to never be needed on this device.
  • _LAZY_PKGS (6 packages): Use Proxy-based lazy loading. The real module loads on first property access. These might be needed depending on runtime conditions.
  • Normal (8 packages): Loaded eagerly as usual. These are core dependencies the gateway always needs.

The dashboard at :9003/dashboard displays live counts of dead, lazy, and loaded packages via the /api/status endpoint.

Implementation

The dead and lazy package lists in hijack.js:

// hijack.js — Two-tier package interception

const Module = require('module');
const _origRequire = Module.prototype.require;

// Packages confirmed NEVER needed on this device
const _DEAD_PKGS = [
  "@anthropic-ai",         // Claude — not configured
  "@google",               // Google GenAI — not configured
  "@aws-sdk", "@aws-crypto", "@aws", "@smithy",  // AWS Bedrock — not configured
  "cohere-ai",             // Cohere — not configured
  "@mistralai",            // Mistral — not configured
  "@huggingface",          // HuggingFace — not configured
  "@cloudflare",           // Cloudflare Workers AI — not configured
  "discord.js", "@discordjs", "@buape/carbon",    // Discord — not configured
  "@slack",                // Slack — not configured
  "@line",                 // LINE messenger — not configured
  "@whiskeysockets",       // WhatsApp via Baileys — not configured
  "libsignal",             // Signal protocol — not configured
  "@larksuiteoapi",        // Lark/Feishu — not configured
  "pdfjs-dist",            // PDF rendering — too heavy (30 MB)
  "sharp",                 // Image processing — native addon not compiled
  "puppeteer",             // Browser automation — no Chrome on device
  "puppeteer-core",        // Same
  "playwright",            // Same
  "fsevents",              // macOS-only file watcher
  "osx-temperature-sensor",// macOS-only
  "canvas",                // Native canvas — not compiled for ARM32
  "dtrace-provider",       // DTrace — not available on Android
];

// Packages that MIGHT be used — deferred until first property access
const _LAZY_PKGS = [
  "axios",
  "cheerio",
  "marked",
  "highlight.js",
  "nodemailer",
  "cron",
];

// Tracking counters for the dashboard
const _pkgStats = { dead: 0, lazy: 0, lazyLoaded: 0 };

The require override that implements the two tiers:

// Frozen empty object — shared by all dead stubs
const _DEAD_STUB = Object.freeze(Object.create(null));

// Check if a require ID matches a dead/lazy package (prefix matching)
function _isDead(id) {
  for (var i = 0; i < _DEAD_PKGS.length; i++) {
    if (id === _DEAD_PKGS[i] || id.startsWith(_DEAD_PKGS[i] + '/')) return true;
  }
  return false;
}

function _isLazy(id) {
  for (var i = 0; i < _LAZY_PKGS.length; i++) {
    if (id === _LAZY_PKGS[i] || id.startsWith(_LAZY_PKGS[i] + '/')) return true;
  }
  return false;
}

// Create a lazy proxy that defers require() until first property access
function _createLazyProxy(id, caller) {
  var loaded = null;
  return new Proxy({}, {
    get: function(target, prop) {
      if (prop === '__esModule') return true;
      if (prop === Symbol.toPrimitive || prop === Symbol.toStringTag) return undefined;
      if (!loaded) {
        loaded = _origRequire.call(caller, id);
        _pkgStats.lazyLoaded++;
      }
      return loaded[prop];
    },
    getOwnPropertyDescriptor: function(target, prop) {
      if (prop === '__esModule') {
        return { value: true, configurable: true, writable: false, enumerable: false };
      }
      if (!loaded) {
        loaded = _origRequire.call(caller, id);
        _pkgStats.lazyLoaded++;
      }
      return Object.getOwnPropertyDescriptor(loaded, prop);
    },
  });
}

// Override Module.prototype.require
Module.prototype.require = function hijackedRequire(id) {
  if (typeof id === 'string') {
    if (_isDead(id)) {
      _pkgStats.dead++;
      return _DEAD_STUB;
    }
    if (_isLazy(id)) {
      _pkgStats.lazy++;
      return _createLazyProxy(id, this);
    }
  }
  return _origRequire.call(this, id);
};

// Expose stats for the dashboard
globalThis.__hijackStats = _pkgStats;

The dashboard API reads the counters:

// In the /api/status route (also in hijack.js)
// Accessible at http://localhost:9000/api/status
{
  packages: {
    dead: __hijackStats.dead,      // number of dead require() calls intercepted
    lazy: __hijackStats.lazy,      // number of lazy proxies created
    lazyLoaded: __hijackStats.lazyLoaded, // number of lazy proxies that actually loaded
    deadCount: _DEAD_PKGS.length,  // 23
    lazyCount: _LAZY_PKGS.length,  // 6
  }
}

Verification

  • Start the gateway and check the dashboard at :9003/dashboard. The package stats section should display dead: 23, lazy: 6.
  • Query the API directly: curl localhost:9000/api/status | jq .packages returns the correct counts.
  • Check RAM usage: ps aux | grep node22-icu shows RSS around 155 MB. Without dead stubs, loading all 23 packages would add roughly 40-60 MB of heap usage.
  • Verify a dead package returns an empty stub:
// In a Node REPL with hijack loaded
const s3 = require('@aws-sdk/client-s3');
console.log(Object.keys(s3));       // [] — empty frozen object
console.log(typeof s3.S3Client);    // 'undefined' — nothing there
  • Verify a lazy package loads on access:
const axios = require('axios');
// At this point, axios is a Proxy — no disk I/O yet
console.log(typeof axios.get);      // 'function' — triggers real require()
// Now check stats: lazyLoaded should have incremented by 1
  • Confirm prefix matching works: require('@aws-sdk/client-s3/dist/cjs/index.js') also returns _DEAD_STUB because the ID starts with @aws-sdk.

Gotchas

  • Dead packages must be truly dead. If any code path calls a method on a dead stub, it gets undefined back instead of the expected function. This causes silent failures (not crashes), which are harder to debug. Audit all code paths before adding a package to _DEAD_PKGS.
  • The __esModule property in the Proxy's getOwnPropertyDescriptor trap must return a descriptor with configurable: true. Babel/TypeScript compiled code checks __esModule and may call Object.defineProperty on the import. If the descriptor says configurable: false, the engine throws "Cannot redefine property".
  • Object.freeze(Object.create(null)) creates an object with no prototype. This means stub.toString() throws a TypeError. If any code calls .toString() or .valueOf() on a dead stub, it will fail. In practice, none of the 23 dead packages' consumers call these methods.
  • The _pkgStats.dead counter increments on every require() call for a dead package, not just the first. A package required 50 times during startup shows dead: 50. This counts interceptions, not unique packages. The dashboard can divide by deadCount for a rough per-package average.
  • Prefix matching (id.startsWith(pkg + '/')) is intentional. npm packages like @aws-sdk have many sub-packages (@aws-sdk/client-s3, @aws-sdk/middleware-retry, etc.). Matching the prefix stubs out the entire scope. This is safe only if the entire scope is dead.
  • Do not add packages with side effects to _DEAD_PKGS. Some packages register global error handlers, modify prototypes, or add process event listeners in their top-level code. Stubbing these out removes the side effects, which may break seemingly unrelated functionality elsewhere.
  • The boundary between dead and lazy is a judgment call based on the device's configuration. A package is dead if the feature it supports has no API keys configured and no way to trigger. A package is lazy if the feature exists but is rarely invoked (e.g., email sending, markdown rendering on demand).

Result

23 packages return instantly as frozen empty stubs with zero RAM cost, zero disk I/O, and zero JavaScript parse time. 6 packages defer their loading until a property is actually accessed. The gateway saves roughly 40-60 MB of potential V8 heap usage from packages that would otherwise be loaded, parsed, and JIT-compiled during startup. The two-tier system is visible on the dashboard (dead: 23, lazy: 6, loaded: 8), making it straightforward to verify that dead packages stay dead and lazy packages load only when triggered. This is one of the critical hacks that keeps the gateway running within its 112 MB V8 heap limit on a 1 GB phone.