esbuild-wasm memory limit causes RangeError: Invalid array length when bundling TypeScript processors with large dependencies
Describe the bug
When a TypeScript processor imports packages with large transitive dependency trees, Artillery's use of esbuild-wasm to bundle the processor fails with RangeError: Invalid array length. This is a hard memory ceiling imposed by the WebAssembly linear memory model — it is not a problem with the user's code or dependencies.
Artillery version: 2.0.30 esbuild-wasm version: ^0.19.12
Steps to reproduce
- Create a TypeScript processor that imports a package with a large transitive dependency tree (e.g. one that pulls in typeorm and its peers).
- Run an Artillery test that references this processor.
- Artillery calls esbuild.buildSync(...) via esbuild-wasm to compile it.
Error output
RangeError: Invalid array length
Root cause
lib/util/prepare-test-execution-plan.js uses esbuild-wasm to bundle TypeScript processors:
// line ~149 and ~191 const esbuild = require('esbuild-wasm'); esbuild.buildSync({ bundle: true, ... });
esbuild-wasm runs inside a WebAssembly sandbox with a fixed linear memory limit. When the bundled output (processor + all transitive dependencies) exceeds that limit, it throws RangeError: Invalid array length. Native esbuild, by contrast, uses a platform binary with no such constraint — the API is identical.
The code itself already contains an acknowledgement of this limitation:
//TODO: move require to top of file when Lambda bundle size issue is solved //must be conditionally required for now as this package is removed in Lambda for now to avoid bigger package sizes const esbuild = require('esbuild-wasm');
We understand esbuild-wasm is used to keep the Lambda deployment package small (the native esbuild binary adds ~7 MB per platform). However, this tradeoff penalises all non-Lambda users — CI runners, local development, dedicated test infrastructure — who have no need for WASM and no benefit from the smaller package size.
Suggested fix
Add a configuration option to select the esbuild variant, for example, via an Artillery config property:
config: { bundling: { esbuild: 'native' // or 'wasm' (default, preserves current behaviour) } } This would let users with large processors opt into native esbuild without affecting Lambda deployments that need esbuild-wasm.
An alternative would be to auto-detect the environment (e.g. check for AWS_LAMBDA_FUNCTION_NAME) and fall back to native esbuild when not running in Lambda.
Workaround
Until this is fixed, it is possible to patch Artillery post-install by replacing require('esbuild-wasm') with require('esbuild') in lib/util/prepare-test-execution-plan.js. Native esbuild must be installed as a dev dependency alongside artillery. The following script can be run as part of your install/CI setup:
const fs = require('fs'); const path = require('path');
const artilleryFilePath = path.join( __dirname, 'node_modules/artillery/lib/util/prepare-test-execution-plan.js' );
if (!fs.existsSync(artilleryFilePath)) { console.log('[patch-artillery] File not found, skipping patch'); process.exit(0); }
const content = fs.readFileSync(artilleryFilePath, 'utf8');
if (!content.includes("require('esbuild-wasm')")) { console.log('[patch-artillery] Already patched — native esbuild is in use'); process.exit(0); }
fs.writeFileSync( artilleryFilePath, content.replaceAll("require('esbuild-wasm')", "require('esbuild')") ); console.log('[patch-artillery] Patched — replaced esbuild-wasm with native esbuild');
This confirms the native esbuild API is a drop-in replacement and the only change needed is the package name.
Source: artilleryio/artillery