module: synchronously load most ES modules by GeoffreyBooth · Pull Request #62530 · nodejs/node
Building on #55782, this PR uses the path @joyeecheung created for require(esm) to synchronously resolve and load all ES modules that lack top-level await, which is the vast majority of modules. The sync path is used when no async loader hooks, --import flags, or --inspect-brk are active; it falls back to the existing async path otherwise. Top-level await presence can only be determined after the module graph is instantiated, so if TLA is detected the already-instantiated graph falls back to async evaluation. In all cases the behavior is identical to the existing async path.
On current main, an ES module graph generates 14 + 5N promises for N modules; so 19 promises for a single module graph (one entry point that doesn’t import anything), 24 promises if that entry point imports one file, 29 promises for a three-module graph and so on.
In this PR, only one promise is created regardless of graph size: the low-level V8 module.evaluate() call that happens within module.evaluateSync(), where an immediately-resolved promise is created even for modules that don’t have top-level await. But still, it’s only one promise for an entire application, no matter how big the app is.
This PR adds a benchmark that focuses on the module loading flow that this PR improves:
confidence improvement accuracy (*) (**) (***)
esm/startup-esm-graph.js n=100 modules='0250' 0.71 % ±3.39% ±4.47% ±5.74%
esm/startup-esm-graph.js n=100 modules='0500' 0.45 % ±3.15% ±4.15% ±5.33%
esm/startup-esm-graph.js n=100 modules='1000' 1.96 % ±3.19% ±4.21% ±5.40%
esm/startup-esm-graph.js n=100 modules='2000' 1.08 % ±3.12% ±4.11% ±5.28%
Be aware that when doing many comparisons the risk of a false-positive
result increases. In this case, there are 4 comparisons, you can thus
expect the following amount of false-positive results:
0.20 false positives, when considering a 5% risk acceptance (*, **, ***),
0.04 false positives, when considering a 1% risk acceptance (**, ***),
0.00 false positives, when considering a 0.1% risk acceptance (***)
So basically it’s within the margin of error.