101 lines
3.3 KiB
JavaScript
101 lines
3.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
/**
|
|
* Measures what the default resolver costs per resolution once the fs cache
|
|
* is warm, by resolving every package in the repo's node_modules from the
|
|
* repo root, alternating cjs/esm conditions like a real run does.
|
|
*
|
|
* The raw `ResolverFactory` number is the floor: the gap between it and
|
|
* `Resolver.findNodeModule` is jest-resolve's own overhead. A
|
|
* `ResolverFactory` construction (`new` or `cloneWithOptions`) is a ~5µs
|
|
* NAPI call - several warm resolutions' worth - so anything that puts one
|
|
* back on the per-resolution path shows up here immediately.
|
|
*
|
|
* Run with: node packages/jest-resolve/__benchmarks__/test.js
|
|
* (requires `yarn build:js`)
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const path = require('node:path');
|
|
const fs = require('graceful-fs');
|
|
const {ResolverFactory} = require('unrs-resolver');
|
|
const Resolver = require('../build').default;
|
|
|
|
const repoRoot = path.resolve(__dirname, '../../..');
|
|
const nodeModules = path.join(repoRoot, 'node_modules');
|
|
|
|
const specifiers = [];
|
|
for (const entry of fs.readdirSync(nodeModules)) {
|
|
if (entry.startsWith('.')) continue;
|
|
if (entry.startsWith('@')) {
|
|
for (const scoped of fs.readdirSync(path.join(nodeModules, entry))) {
|
|
specifiers.push(`${entry}/${scoped}`);
|
|
}
|
|
} else {
|
|
specifiers.push(entry);
|
|
}
|
|
}
|
|
|
|
const extensions = ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts'];
|
|
const cjsConditions = ['require', 'node', 'default'];
|
|
const esmConditions = ['import', 'module-sync', 'default'];
|
|
|
|
function resolveAllViaFindNodeModule() {
|
|
let resolved = 0;
|
|
for (const [i, specifier] of specifiers.entries()) {
|
|
const result = Resolver.findNodeModule(specifier, {
|
|
basedir: repoRoot,
|
|
conditions: i % 2 === 0 ? cjsConditions : esmConditions,
|
|
extensions,
|
|
moduleDirectory: ['node_modules'],
|
|
});
|
|
if (result) resolved++;
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
const cjsFactory = new ResolverFactory({
|
|
conditionNames: cjsConditions,
|
|
extensions,
|
|
modules: ['node_modules'],
|
|
});
|
|
const esmFactory = cjsFactory.cloneWithOptions({
|
|
conditionNames: esmConditions,
|
|
extensions,
|
|
modules: ['node_modules'],
|
|
});
|
|
|
|
function resolveAllViaRawFactory() {
|
|
let resolved = 0;
|
|
for (const [i, specifier] of specifiers.entries()) {
|
|
const factory = i % 2 === 0 ? cjsFactory : esmFactory;
|
|
if (factory.sync(repoRoot, specifier).path) resolved++;
|
|
}
|
|
return resolved;
|
|
}
|
|
|
|
function bench(label, resolveAll, rounds) {
|
|
resolveAll(); // warm the fs cache so the numbers measure CPU, not IO
|
|
const start = process.hrtime.bigint();
|
|
let resolved = 0;
|
|
for (let round = 0; round < rounds; round++) {
|
|
resolved = resolveAll();
|
|
}
|
|
const ns = Number(process.hrtime.bigint() - start);
|
|
const perCall = ns / (rounds * specifiers.length);
|
|
console.log(
|
|
`${label.padEnd(26)} ${(ns / 1e6 / rounds).toFixed(2).padStart(8)} ms/round ${perCall.toFixed(0).padStart(6)} ns/resolution (${resolved}/${specifiers.length} resolved)`,
|
|
);
|
|
}
|
|
|
|
const ROUNDS = 30;
|
|
console.log(`${specifiers.length} specifiers, ${ROUNDS} rounds\n`);
|
|
bench('Resolver.findNodeModule', resolveAllViaFindNodeModule, ROUNDS);
|
|
bench('raw ResolverFactory (floor)', resolveAllViaRawFactory, ROUNDS);
|