Skip to content

Instantly share code, notes, and snippets.

@pbzona
Created November 26, 2025 22:29
Show Gist options
  • Select an option

  • Save pbzona/87b5b9b3d3f45993bb9fb8e7506d72dc to your computer and use it in GitHub Desktop.

Select an option

Save pbzona/87b5b9b3d3f45993bb9fb8e7506d72dc to your computer and use it in GitHub Desktop.
ESM import benchmark
/**
* Module Import Performance Benchmark (ESM)
*
* Measures ESM dynamic import() time for any TypeScript/JavaScript file.
* Runs 100 iterations in fresh Node.js subprocesses to simulate cold-start.
* Uses SWC to parse imports and separately benchmarks each dependency.
*
* Output:
* - Average and p95 import time (100 runs)
* - NPM package dependencies with individual times
* - Local imports (@/ and ./) with individual times
* - Export count and importer count
* - JSON summary for sharing
*
* Usage:
* pnpm tsx scripts/benchmark-import.ts <file-path>
*
* Examples:
* pnpm tsx scripts/benchmark-import.ts src/db/schema.ts
* pnpm tsx scripts/benchmark-import.ts src/lib/user.ts
*/
import { execSync } from 'child_process';
import { cpus } from 'os';
import { readFileSync, statSync } from 'fs';
import { resolve, relative, basename, dirname } from 'path';
import { parseSync } from '@swc/core';
const WARMUP_RUNS = 3;
const BENCHMARK_RUNS = 100;
interface TargetFile {
absolutePath: string;
relativePath: string;
fileName: string;
requirePath: string;
}
function parseArgs(): TargetFile {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: pnpm tsx scripts/benchmark-import.ts <file-path>');
process.exit(1);
}
const inputPath = args[0];
const absolutePath = resolve(process.cwd(), inputPath);
const relativePath = relative(process.cwd(), absolutePath);
try {
statSync(absolutePath);
} catch {
console.error(`Error: File not found: ${absolutePath}`);
process.exit(1);
}
return {
absolutePath,
relativePath,
fileName: basename(relativePath),
requirePath: './' + relativePath.replace(/\.tsx?$/, ''),
};
}
function formatMs(ms: number): string {
return `${ms.toFixed(1)}ms`;
}
interface ImportInfo {
path: string;
type: 'npm' | 'local';
resolvePath: string; // Path to use with import()
}
function extractRuntimeImports(filePath: string): ImportInfo[] {
const content = readFileSync(filePath, 'utf-8');
const imports: ImportInfo[] = [];
const seen = new Set<string>();
// Use SWC to parse the file
const ast = parseSync(content, {
syntax: 'typescript',
tsx: filePath.endsWith('.tsx'),
});
for (const item of ast.body) {
// Skip type-only imports (ImportDeclaration with typeOnly: true)
if (item.type === 'ImportDeclaration' && !item.typeOnly) {
const importPath = item.source.value;
if (importPath.startsWith('@/')) {
// Local alias import (@/lib/foo -> ./src/lib/foo)
if (!seen.has(importPath)) {
seen.add(importPath);
const localPath = './src/' + importPath.slice(2);
imports.push({ path: importPath, type: 'local', resolvePath: localPath });
}
} else if (importPath.startsWith('.')) {
// Relative import (./foo, ../bar)
if (!seen.has(importPath)) {
seen.add(importPath);
const fileDir = dirname(filePath);
const resolvedPath = './' + relative(process.cwd(), resolve(fileDir, importPath));
imports.push({ path: importPath, type: 'local', resolvePath: resolvedPath });
}
} else {
// npm package - dedupe by package name (e.g., drizzle-orm/pg-core -> drizzle-orm)
const packageName = importPath.startsWith('@')
? importPath.split('/').slice(0, 2).join('/')
: importPath.split('/')[0];
if (!seen.has(packageName)) {
seen.add(packageName);
imports.push({ path: packageName, type: 'npm', resolvePath: packageName });
}
}
}
}
return imports;
}
function runBenchmark(modulePath: string, runs: number): number[] {
const results: number[] = [];
// Use ESM dynamic import() to match "import * as schema" behavior
// This is closer to how Next.js/SWC processes imports
const script = `
console.log=console.error=console.warn=()=>{};
const s=performance.now();
import('${modulePath}').then(m=>{
process.stdout.write(String(performance.now()-s));
}).catch(()=>{
process.stdout.write('0');
});
`.replace(/\n\s*/g, '');
for (let i = 0; i < runs; i++) {
try {
const output = execSync(`node --experimental-vm-modules -e "${script}"`, {
cwd: process.cwd(),
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, NODE_OPTIONS: '--import tsx' },
});
const time = parseFloat(output.trim());
if (!isNaN(time) && time > 0) results.push(time);
} catch {
// Skip failed runs
}
}
return results;
}
function stats(runs: number[]): {
mean: number;
p50: number;
p95: number;
min: number;
max: number;
} {
if (runs.length === 0) return { mean: 0, p50: 0, p95: 0, min: 0, max: 0 };
const sorted = [...runs].sort((a, b) => a - b);
const mean = runs.reduce((a, b) => a + b, 0) / runs.length;
const p50 = sorted[Math.floor(sorted.length * 0.5)];
const p95 = sorted[Math.floor(sorted.length * 0.95)];
const min = sorted[0];
const max = sorted[sorted.length - 1];
return { mean, p50, p95, min, max };
}
function countImporters(targetFile: TargetFile): number {
if (!targetFile.relativePath.startsWith('src/')) return 0;
const aliasPath = targetFile.relativePath.replace(/^src\//, '@/').replace(/\.tsx?$/, '');
try {
const output = execSync(
`grep -r "from '${aliasPath}'" src --include='*.ts' --include='*.tsx' 2>/dev/null | wc -l`,
{ cwd: process.cwd(), encoding: 'utf-8' }
);
return parseInt(output.trim()) || 0;
} catch {
return 0;
}
}
async function main() {
const target = parseArgs();
const fileContent = readFileSync(target.absolutePath, 'utf-8');
const fileStats = statSync(target.absolutePath);
const lines = fileContent.split('\n').length;
const sizeKB = Math.round((fileStats.size / 1024) * 10) / 10;
console.log(`\nπŸ“¦ ${target.relativePath}`);
console.log(` ${lines} lines, ${sizeKB} KB\n`);
// Cold-start benchmark - detect if file errors on load (using ESM import)
const checkScript = `
const c=console.log;console.log=console.error=console.warn=()=>{};
import('${target.requirePath}').then(()=>c('OK')).catch(e=>c('ERR:'+e.message));
`.replace(/\n\s*/g, '');
let loadError: string | null = null;
try {
const checkResult =
execSync(`node --experimental-vm-modules -e "${checkScript}"`, {
cwd: process.cwd(),
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, NODE_OPTIONS: '--import tsx' },
})
.trim()
.split('\n')
.pop() || '';
if (checkResult.startsWith('ERR:')) {
loadError = checkResult.slice(4);
}
} catch {
loadError = 'Unknown error';
}
let runs: number[] = [];
if (loadError) {
console.log(
` ⚠️ File errors on load: ${loadError.slice(0, 50)}${loadError.length > 50 ? '...' : ''}`
);
console.log(' Using dependency times as estimate.\n');
} else {
process.stdout.write(` Benchmarking (${BENCHMARK_RUNS} runs)`);
for (let i = 0; i < WARMUP_RUNS; i++) {
runBenchmark(target.requirePath, 1);
}
for (let i = 0; i < BENCHMARK_RUNS; i++) {
const result = runBenchmark(target.requirePath, 1);
if (result.length > 0) runs.push(result[0]);
if (i % 20 === 0) process.stdout.write('.');
}
process.stdout.write(' done\n\n');
}
// Dependency breakdown
const deps = extractRuntimeImports(target.absolutePath);
const depTimes: { name: string; time: number; type: 'npm' | 'local' }[] = [];
if (deps.length > 0) {
for (const dep of deps) {
const depRuns = runBenchmark(dep.resolvePath, 5);
const depMean = depRuns.length > 0 ? depRuns.reduce((a, b) => a + b, 0) / depRuns.length : -1;
depTimes.push({ name: dep.path, time: depMean, type: dep.type });
}
}
// Count exports (suppress console output)
let exportCount = 0;
const origLog = console.log;
const origErr = console.error;
const origWarn = console.warn;
try {
console.log = console.error = console.warn = () => {};
const importPath = '../' + target.relativePath.replace(/\.tsx?$/, '');
const mod = await import(importPath);
exportCount = Object.keys(mod).length;
} catch {
// Ignore
} finally {
console.log = origLog;
console.error = origErr;
console.warn = origWarn;
}
const importerCount = countImporters(target);
const { mean, p50, p95, min, max } = stats(runs);
// Calculate totals for npm and local deps
const npmDeps = depTimes.filter(d => d.type === 'npm' && d.time > 0);
const localDeps = depTimes.filter(d => d.type === 'local' && d.time > 0);
const totalDepTime = depTimes.reduce((sum, d) => sum + (d.time > 0 ? d.time : 0), 0);
// If file errored, estimate from deps
const estimatedFromDeps = !!(loadError && runs.length === 0 && totalDepTime > 0);
const displayMean = estimatedFromDeps ? totalDepTime : mean;
const displayP50 = estimatedFromDeps ? totalDepTime : p50;
const displayP95 = estimatedFromDeps ? totalDepTime : p95;
const displayMin = estimatedFromDeps ? totalDepTime : min;
const displayMax = estimatedFromDeps ? totalDepTime : max;
// Output
if (displayMean > 0) {
const label = estimatedFromDeps ? 'Estimated total' : 'Total import time (includes all deps)';
console.log(` ${label}:`);
console.log(` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”`);
console.log(` β”‚ min β”‚ p50 β”‚ avg β”‚ p95 β”‚ max β”‚`);
console.log(` β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€`);
console.log(
` β”‚${formatMs(displayMin).padStart(7)} β”‚${formatMs(displayP50).padStart(7)} β”‚${formatMs(displayMean).padStart(7)} β”‚${formatMs(displayP95).padStart(7)} β”‚${formatMs(displayMax).padStart(7)} β”‚`
);
console.log(` β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜`);
console.log();
}
if (npmDeps.length > 0 || localDeps.length > 0) {
console.log(' Dependencies (measured in isolation):');
for (const dep of [...npmDeps, ...localDeps].sort((a, b) => b.time - a.time)) {
const tag = dep.type === 'local' ? ' (local)' : '';
console.log(` ${(dep.name + tag).padEnd(36)} ${formatMs(dep.time).padStart(8)}`);
}
console.log();
}
if (exportCount > 0 || importerCount > 0) {
console.log(' Stats:');
if (exportCount > 0) console.log(` Exports: ${exportCount}`);
if (importerCount > 0) console.log(` Imported by: ~${importerCount} files`);
console.log();
}
// JSON for sharing
const summary = {
file: target.relativePath,
lines,
sizeKB,
exports: exportCount,
importedBy: importerCount,
avgMs: Math.round(displayMean * 10) / 10,
p50Ms: Math.round(displayP50 * 10) / 10,
p95Ms: Math.round(displayP95 * 10) / 10,
minMs: Math.round(displayMin * 10) / 10,
maxMs: Math.round(displayMax * 10) / 10,
estimated: estimatedFromDeps,
npmDeps: npmDeps.map(d => ({ name: d.name, ms: Math.round(d.time * 10) / 10 })),
localDeps: localDeps.map(d => ({ name: d.name, ms: Math.round(d.time * 10) / 10 })),
system: `${process.platform}/${process.arch}, Node ${process.version}, ${cpus()[0]?.model || 'Unknown CPU'}`,
timestamp: new Date().toISOString(),
};
console.log(' JSON:');
console.log(' ' + JSON.stringify(summary));
console.log();
}
main().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment