Version 0.11.3 addresses a critical runtime issue that was blocking all bot execution in the Screeps sandbox environment. This release demonstrates the importance of build-time configuration and environment isolation for cross-platform JavaScript deployment.
The Problem
After a recent code change, the bot bundle began referencing Node.js-specific globals that don’t exist in the Screeps runtime environment:
1 | ReferenceError: process is not defined |
This error occurred because feature flag checks like process.env.TASK_SYSTEM_ENABLED were being emitted directly into the bundle. While this works perfectly in Node.js environments (tests, local development), it causes immediate failures in the Screeps sandbox, which doesn’t provide the process global.
The Root Cause
The issue stemmed from how environment variables were being checked in the source code:
1 | // This pattern works in Node.js but fails in Screeps |
Previously, our esbuild configuration wasn’t configured to handle these environment checks at build time, so they leaked into the final bundle as runtime checks. Any deployed code that referenced process, __dirname, __filename, or require() would immediately crash in the Screeps environment.
The Solution
We updated the esbuild configuration in scripts/buildProject.ts to replace environment variable references with literal values at build time:
1 | // Build configuration now includes: |
This approach ensures that:
- Build-time Replacement: Environment checks are resolved during bundling, not at runtime
- No Runtime Dependencies: The final bundle contains no Node.js-specific globals
- Safe Defaults: Variables default to
"false"if not set during build - Explicit Opt-in: Features can be enabled by setting environment variables during build:
TASK_SYSTEM_ENABLED=true bun run build
Example Transformation
Before bundling (source code):
1 | if (process.env.TASK_SYSTEM_ENABLED === "true") { |
After bundling (with TASK_SYSTEM_ENABLED not set):
1 | if ("false" === "true") { |
esbuild’s dead code elimination then removes this entire block during minification, resulting in zero runtime overhead for disabled features.
Technical Deep Dive
Why Build-Time Over Runtime?
We considered several approaches to solve this problem:
Runtime Environment Detection: Check for
processexistence before use- ❌ Still adds unnecessary code to bundle
- ❌ Runtime overhead for every check
- ❌ Doesn’t handle build-time optimization opportunities
Conditional Bundling: Separate bundles for different environments
- ❌ Complex build matrix
- ❌ Maintenance burden
- ❌ Deployment complexity
Build-Time Variable Replacement (chosen solution)
- ✅ Zero runtime overhead
- ✅ Dead code elimination removes disabled features
- ✅ Single bundle works everywhere
- ✅ Explicit build-time configuration
The build-time replacement approach provides the best balance of simplicity, performance, and correctness.
Memory-Based Feature Flags Still Work
Importantly, runtime feature flags stored in memory continue to work perfectly:
1 | // This pattern is safe and supported |
Memory-based flags are ideal for features that need to be toggled during runtime without redeployment. Build-time flags (via environment variables) are better for features that fundamentally change the bundle structure or should be permanently enabled/disabled for a deployment.
Regression Prevention
To ensure this type of issue never happens again, we added a comprehensive regression test in tests/regression/nodejs-globals-bundle.test.ts:
1 | describe("Bundle Node.js Globals Validation", () => { |
This test runs on every commit and will catch any code that accidentally introduces Node.js dependencies into the bundle.
Impact
This fix unblocks all bot deployment and restores normal operation. Key improvements:
- ✅ Immediate Runtime Stability: Bot executes successfully in Screeps sandbox
- ✅ Build-Time Optimization: Dead code elimination removes disabled features entirely
- ✅ Future-Proof: Regression test prevents reintroduction of Node.js globals
- ✅ Zero Performance Impact: No runtime overhead for environment checks
Configuration
To enable optional features at build time:
1 | # Enable task system |
Features default to disabled ("false") if environment variables aren’t set.
Related Files
scripts/buildProject.ts- Build configuration with environment variable definitionstests/regression/nodejs-globals-bundle.test.ts- Regression test for bundle puritysrc/runtime/bootstrap/Kernel.ts- Uses feature flags for conditional logic
Lessons Learned
This incident reinforces several important principles:
- Cross-Platform JavaScript is Hard: Code that works in Node.js may not work elsewhere
- Build-Time Configuration Matters: Proper build tooling prevents entire classes of runtime errors
- Regression Tests are Essential: Automated validation catches issues before deployment
- Environment Isolation: Clear separation between build-time and runtime concerns prevents leakage
What’s Next
Future improvements to the build system:
- Static Analysis: Additional linting rules to catch Node.js global usage in source code
- Build Validation: Pre-deployment checks to validate bundle compatibility
- Documentation: Guidelines for safe feature flag patterns in cross-platform code
Full Changelog: 0.11.3 on GitHub