Skip to content

Replace seedrandom package with inline ESM implementation#3650

Open
andreialecu wants to merge 2 commits intojosdejong:developfrom
andreialecu:replace-seedrandom-with-esm
Open

Replace seedrandom package with inline ESM implementation#3650
andreialecu wants to merge 2 commits intojosdejong:developfrom
andreialecu:replace-seedrandom-with-esm

Conversation

@andreialecu
Copy link
Copy Markdown

Summary

  • Replaces the CommonJS-only seedrandom dependency with an inline ESM module based on the same ARC4 (RC4) cipher algorithm
  • Eliminates the "Module 'seedrandom' is not ESM" warning in Angular and other ESM-first bundlers
  • Enables proper tree-shaking of this code

Part of #3649

Details

The seedrandom package hasn't been updated in 7+ years and only ships CommonJS. Rather than waiting for an upstream ESM build, this inlines a minimal implementation that only includes what mathjs needs (the double() generator).

The implementation produces identical output to the original seedrandom package — verified by exact sequence stability tests for multiple seeds.

Test plan

  • New unit tests for the seedrandom module (determinism, output range, distribution, exact sequence stability, edge cases)
  • Existing seededrandom.test.js integration tests pass
  • All 6656 unit tests pass
  • Lint passes with zero warnings

Replaces the CommonJS-only `seedrandom` dependency with an inline ESM
module based on the same ARC4 (RC4) cipher algorithm. This eliminates
the "Module 'seedrandom' is not ESM" warning in Angular and other
ESM-first bundlers, and enables proper tree-shaking.

Refs josdejong#3649
@andreialecu
Copy link
Copy Markdown
Author

andreialecu commented Mar 26, 2026

You can verify here that the code in this PR produces the exact same sequence as the seedrandom npm lib:

https://codesandbox.io/p/devbox/frosty-pond-jq6qqt?workspaceId=ws_HeHwxRY4bpopppVUoiziuV

V8 optimizes the tight while(count--) loop better than the manually
unrolled version. Results in 20-28% faster bulk generation and 56-58%
faster for realistic create+generate usage patterns.
@andreialecu
Copy link
Copy Markdown
Author

andreialecu commented Mar 26, 2026

Benchmark script

npm install --no-save seedrandom@3.0.5

node --input-type=module --eval "
import { createRequire } from 'module';
const require = createRequire(import.meta.url + '/../');
const originalSeedrandom = require('seedrandom');
const { seedrandom } = await import('./src/function/probability/util/seedrandom.js');

const seeds = ['hello', 'test123', '42', 'a longer seed with spaces'];
const iterations = 500;
const numPerIter = 10000;

console.log('Generate 10,000 random numbers per iteration (' + iterations + ' iterations):');
console.log('');
console.log('Seed                     | Original | Inline  | Speedup');
console.log('-------------------------|----------|---------|--------');

for (const seed of seeds) {
  for (let i = 0; i < 50; i++) { const r = originalSeedrandom(seed); for (let j = 0; j < numPerIter; j++) r(); }
  for (let i = 0; i < 50; i++) { const r = seedrandom(seed); for (let j = 0; j < numPerIter; j++) r(); }

  let start = performance.now();
  for (let i = 0; i < iterations; i++) { const r = originalSeedrandom(seed); for (let j = 0; j < numPerIter; j++) r(); }
  const orig = (performance.now() - start) / iterations;

  start = performance.now();
  for (let i = 0; i < iterations; i++) { const r = seedrandom(seed); for (let j = 0; j < numPerIter; j++) r(); }
  const inl = (performance.now() - start) / iterations;

  const ratio = (orig / inl).toFixed(1);
  console.log(seed.padEnd(25) + '| ' + orig.toFixed(3).padStart(6) + 'ms | ' + inl.toFixed(3).padStart(5) + 'ms | ' + ratio + 'x');
}

console.log('');
console.log('Realistic usage (create + generate N numbers, 100k iterations):');
console.log('');
console.log('Numbers/call | Original | Inline  | Speedup');
console.log('-------------|----------|---------|--------');

for (const count of [1, 5, 10, 50, 100]) {
  const iter = 100000;
  for (let i = 0; i < 1000; i++) { const r = originalSeedrandom('seed'); for (let j = 0; j < count; j++) r(); }
  for (let i = 0; i < 1000; i++) { const r = seedrandom('seed'); for (let j = 0; j < count; j++) r(); }

  let start = performance.now();
  for (let i = 0; i < iter; i++) { const r = originalSeedrandom('seed'); for (let j = 0; j < count; j++) r(); }
  const orig = (performance.now() - start) / iter;

  start = performance.now();
  for (let i = 0; i < iter; i++) { const r = seedrandom('seed'); for (let j = 0; j < count; j++) r(); }
  const inl = (performance.now() - start) / iter;

  const ratio = (orig / inl).toFixed(1);
  console.log(String(count).padEnd(13) + '| ' + (orig * 1000).toFixed(1).padStart(6) + 'µs | ' + (inl * 1000).toFixed(1).padStart(5) + 'µs | ' + ratio + 'x');
}

console.log('');
for (const seed of seeds) {
  const o = originalSeedrandom(seed); const n = seedrandom(seed);
  let match = true;
  for (let i = 0; i < 100; i++) { if (o() !== n()) { match = false; break; } }
  console.log(seed + ': ' + (match ? 'identical' : 'MISMATCH'));
}
"

Results (MBP M1 Max):

Generate 10,000 random numbers per iteration (500 iterations):

Seed                     | Original | Inline  | Speedup
-------------------------|----------|---------|--------
hello                    |  0.437ms | 0.335ms | 1.3x
test123                  |  0.449ms | 0.363ms | 1.2x
42                       |  0.472ms | 0.351ms | 1.3x
a longer seed with spaces|  0.440ms | 0.365ms | 1.2x

Realistic usage (create + generate N numbers, 100k iterations):

Numbers/call | Original | Inline  | Speedup
-------------|----------|---------|--------
1            |    3.3µs |   1.3µs | 2.6x
5            |    3.2µs |   1.3µs | 2.4x
10           |    3.3µs |   1.5µs | 2.2x
50           |    5.0µs |   2.9µs | 1.7x
100          |    7.1µs |   4.6µs | 1.6x

hello: identical
test123: identical
42: identical
a longer seed with spaces: identical

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant