Repository navigation
ESM import of http is slower compared to CommonJS #59686
Description
Activity
Oh, this was likely reported in #57649 already!
I think it comes from the fact that at evaluation time, the ESM built-in facade needs access all properties of the built-in and set them on the namespace object. I think https://github.com/tc39/proposal-defer-import-eval might be able to help with this
Reacted by Robo and SylphyYeah, deferred imports or just lazy import at the point of use helps us, but that makes one wonder what other core modules should be treated like this, would be great to get that documented somewhere.
All built-in modules are treated like this - all properties are eagerly evaluated and populated at evaluation for ESM, compared to in CommonJS things are a lot more dynamic and lazy. Not sure how detailed this should be documented though, because it is more of an implication of the ESM semantics that everything is static v.s. in CommonJS, things are a lot more dynamic, and somewhat implied by e.g. https://nodejs.org/api/esm.html#built-in-modules and the existence of https://nodejs.org/api/module.html#modulesyncbuiltinesmexports - I guess a note can be added to remind users that built-in loaded from ESM is static and always eagerly populated in entirety.
- addeddocIssues and PRs related to Node.js documentation.Issues and PRs related to Node.js documentation.good first issueIssues that are suitable for first-time contributors.Issues that are suitable for first-time contributors.
on Sep 1, 2025 I guess a note can be added to remind users that built-in loaded from ESM is static and always eagerly populated in entirety.
That, but maybe also a call out to certain internal modules that do heavy lifting when imported like this, or even for common JS when certain properties are accessed.
I think it comes from the fact that at evaluation time, the ESM built-in facade needs access all properties of the built-in and set them on the namespace object.
Yup, builtinStrategy -> getESMFacade -> syncExports

but maybe also a call out to certain internal modules that do heavy lifting when imported like this
That might be a bit too much implementation detail to document and can easily get out of sync as people try to optimize it e.g. slicing undici up or something
I'd like to take up the issue.
Thanks for the explanation — that makes sense now! From what I saw in my benchmarks, the slowdown with import is just because ESM eagerly evaluates all properties in the built-in facades, while require stays lazy.
In practice:
Servers don’t really feel it since the cost is one-time.
Small scripts or CLIs can feel slower (~8–10 ms difference), so require or createRequire is still better there.
Tried a few workarounds (dynamic import(), caching, workers), they help but don’t fully remove the overhead.
So for now it looks like: use import for long-running apps, stick with require if startup time matters.
I'd like to take up the issue
Reacted by Hesam DanaeePerformance issues can be tricky. Have you profiled the code to identify bottlenecks? I'd be interested in helping optimize this if you can share some performance metrics or benchmarks.
Version
v22.18.0
Platform
Subsystem
No response
What steps will reproduce the bug?
Run the following performance test from the command line:
How often does it reproduce? Is there a required condition?
Always when I run the performance test.
What is the expected behavior? Why is that the expected behavior?
Importing a constant from
httpshould not be significantly slower when usingimportvsrequire.What do you see instead?
Using
importis a lot slower:Additional information
Is it possible that the lazy undici method gets run when using
import?node/lib/http.js
Lines 118 to 124 in 5af0355