Skip to content

Performance optimizations #2424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
May 20, 2025
Merged

Performance optimizations #2424

merged 28 commits into from
May 20, 2025

Conversation

ardatan
Copy link
Owner

@ardatan ardatan commented May 13, 2025

  • Avoid creating AbortController and AbortSignal if not needed with new Request because it is expensive
  • Avoid creating a map for Headers and try to re-use the init object for Headers for performance with a single-line writeHead.
  • Avoid creating Buffer for string bodies for performance
  • Use setHeaders which accepts Headers since Node 18 if needed to forward Headers to Node

Copy link
Contributor

coderabbitai bot commented May 13, 2025

Warning

Rate limit exceeded

@ardatan has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d65d7be and b3c1d94.

📒 Files selected for processing (1)
  • packages/node-fetch/src/Response.ts (1 hunks)
📝 Walkthrough

Summary by CodeRabbit

  • Performance Improvements

    • Enhanced performance for Node.js 24 by optimizing event handling, header management, and body processing in fetch and server packages.
    • Reduced unnecessary object and buffer creation, improving efficiency for request and response handling.
  • New Features

    • Added benchmarking capabilities with new scripts and configuration files to measure and compare server adapter performance.
  • Bug Fixes

    • Improved error handling for JSON parsing in request handlers.
  • Chores

    • Updated development dependencies and scripts for benchmarking and TypeScript path resolution.
    • Adjusted TypeScript and ESLint configurations for better compatibility with new test and config files.
  • Tests

    • Introduced new benchmark tests to evaluate server performance under different configurations.

Summary by CodeRabbit

  • Performance Improvements

    • Optimized request and response handling for Node.js 24, reducing unnecessary object creation and improving header and body processing efficiency.
    • Enhanced header management for better performance and consistency.
  • New Features

    • Added benchmarking capabilities with new scripts and configuration for performance testing.
    • Introduced internal options for server adapters to fine-tune abort controller and header writing behavior.
  • Bug Fixes

    • Improved error handling for JSON parsing in server adapters.
  • Tests

    • Added benchmark tests to measure server performance under different configurations.
  • Chores

    • Updated development dependencies and scripts to support Vitest benchmarking.
    • Adjusted TypeScript and ESLint configurations for new test and config files.

Walkthrough

This update introduces Node.js 24-focused performance optimizations for the @whatwg-node packages, adds benchmarking infrastructure using Vitest, and implements internal refactoring for improved abort signal and header handling. New benchmarking scripts and configuration files are added, and several internal APIs are adjusted for efficiency and maintainability. A new benchmark test suite is included for performance measurement.

Changes

File(s) Change Summary
.changeset/curvy-seas-smoke.md Documents the performance optimizations and internal refactoring for Node.js 24 in relevant packages.
packages/node-fetch/src/Body.ts, packages/node-fetch/src/Request.ts, packages/node-fetch/src/Response.ts, packages/node-fetch/src/Headers.ts Refactors internal handling of abort signals, header normalization, and string body processing for efficiency and consistency.
packages/node-fetch/src/fetchCurl.ts, packages/node-fetch/src/fetchNodeHttp.ts, packages/node-fetch/src/utils.ts Updates abort signal usage and header object extraction for improved performance and compatibility.
packages/server/src/createServerAdapter.ts, packages/server/src/utils.ts Adds internal flags for custom abort controller and single write head; updates request/response normalization and introduces a custom abort signal class.
packages/server/test/server.bench.ts Adds a new benchmark test suite to measure server adapter performance under various configurations.
package.json Adds a bench script and new dev dependencies (vite-tsconfig-paths, vitest) for benchmarking.
eslint.config.mjs Expands devDependency file patterns to include new Vitest config files.
tsconfig.build.json, tsconfig.json Updates TypeScript config to include/exclude new Vitest configuration files.
vitest.config.ts, vitest.projects.ts Introduces Vitest and workspace configuration files for benchmarking and test path resolution.
packages/server/test/reproductions.spec.ts Adds explicit error handling for JSON parsing in a test case.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant NodeServer
    participant ServerAdapter
    participant RequestHandler

    Client->>NodeServer: Sends HTTP Request
    NodeServer->>ServerAdapter: Passes request, response, options (__useCustomAbortCtrl, __useSingleWriteHead)
    ServerAdapter->>RequestHandler: Normalizes request (may use custom abort controller)
    RequestHandler-->>ServerAdapter: Returns response
    ServerAdapter->>NodeServer: Sends response (may use single writeHead or setHeaders)
    NodeServer-->>Client: Returns HTTP Response
Loading

Possibly related PRs

  • Update Node 24 #2414: Introduces Node.js 24 support and uses the once function from node:events for event handling in node-fetch, closely related to the performance improvements in this PR.

Suggested reviewers

  • EmrysMyrddin
  • enisdenjo

Poem

In the warren of code, a rabbit did dash,
Optimizing fetches for Node’s latest bash.
With signals and headers, so neatly arranged,
And benchmarks to measure how swiftly things changed.
Now with Vitest in tow and performance in sight,
This update brings speed—oh what a delight!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate Unit Tests
  • Create PR with Unit Tests
  • Commit Unit Tests in branch avoid-signal
  • Post Copyable Unit Tests in Comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai auto-generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented May 13, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@whatwg-node/fetch 0.10.8-alpha-20250520114553-b3c1d944611af62fcdc5669e94e8ddc3db8bc593 npm ↗︎ unpkg ↗︎
@whatwg-node/node-fetch 0.7.21-alpha-20250520114553-b3c1d944611af62fcdc5669e94e8ddc3db8bc593 npm ↗︎ unpkg ↗︎
@whatwg-node/server 0.10.9-alpha-20250520114553-b3c1d944611af62fcdc5669e94e8ddc3db8bc593 npm ↗︎ unpkg ↗︎

Copy link
Contributor

github-actions bot commented May 13, 2025

@benchmarks/node-fetch results (noConsumeBody)

   ✓ active_handles.................: avg=141.183969 min=43      med=140     max=171     p(90)=161     p(95)=163    
     data_received..................: 17 MB  564 kB/s
     data_sent......................: 11 MB  366 kB/s
     http_req_blocked...............: avg=2.3µs      min=721ns   med=1.2µs   max=3.35ms  p(90)=2.01µs  p(95)=2.25µs 
     http_req_connecting............: avg=591ns      min=0s      med=0s      max=3.07ms  p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=27.03ms    min=4.26ms  med=25.68ms max=1.1s    p(90)=34.42ms p(95)=38.85ms
       { expected_response:true }...: avg=27.03ms    min=4.26ms  med=25.68ms max=1.1s    p(90)=34.42ms p(95)=38.85ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 110688
     http_req_receiving.............: avg=31.25µs    min=9.86µs  med=23.57µs max=11.67ms p(90)=38.13µs p(95)=44.1µs 
     http_req_sending...............: avg=10.66µs    min=3.5µs   med=5.9µs   max=6.86ms  p(90)=9.73µs  p(95)=12.68µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s      p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=26.99ms    min=4.23ms  med=25.64ms max=1.1s    p(90)=34.37ms p(95)=38.8ms 
     http_reqs......................: 110688 3689.022284/s
     iteration_duration.............: avg=54.16ms    min=28.81ms med=50.77ms max=1.12s   p(90)=62.98ms p(95)=67.52ms
     iterations.....................: 55319  1843.677939/s
     vus............................: 0      min=0         max=100 
     vus_max........................: 100    min=100       max=100 

Copy link
Contributor

github-actions bot commented May 13, 2025

@benchmarks/node-fetch results (consumeBody)

   ✓ active_handles.................: avg=139.653606 min=13      med=138     max=185    p(90)=161     p(95)=169    
     data_received..................: 15 MB  496 kB/s
     data_sent......................: 9.5 MB 318 kB/s
     http_req_blocked...............: avg=3.76µs     min=671ns   med=1.32µs  max=6.72ms p(90)=2.15µs  p(95)=2.56µs 
     http_req_connecting............: avg=2.02µs     min=0s      med=0s      max=6.69ms p(90)=0s      p(95)=0s     
     http_req_duration..............: avg=30.76ms    min=3.13ms  med=29.25ms max=1.35s  p(90)=39.19ms p(95)=44.25ms
       { expected_response:true }...: avg=30.76ms    min=3.13ms  med=29.25ms max=1.35s  p(90)=39.19ms p(95)=44.25ms
     http_req_failed................: 0.00%  ✓ 0           ✗ 97248
     http_req_receiving.............: avg=34.67µs    min=11.52µs med=27.96µs max=8.99ms p(90)=42.12µs p(95)=48.48µs
     http_req_sending...............: avg=11.16µs    min=3.81µs  med=7µs     max=7.35ms p(90)=10.95µs p(95)=14.49µs
     http_req_tls_handshaking.......: avg=0s         min=0s      med=0s      max=0s     p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=30.71ms    min=3.06ms  med=29.2ms  max=1.35s  p(90)=39.14ms p(95)=44.2ms 
     http_reqs......................: 97248  3241.171253/s
     iteration_duration.............: avg=61.62ms    min=12.24ms med=57.97ms max=1.38s  p(90)=71.37ms p(95)=76.75ms
     iterations.....................: 48595  1619.619088/s
     vus............................: 42     min=42        max=100
     vus_max........................: 100    min=100       max=100

Copy link
Contributor

github-actions bot commented May 13, 2025

@benchmarks/server results (ponyfill)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 306338      ✗ 0     
     data_received..................: 30 MB   1.0 MB/s
     data_sent......................: 12 MB   408 kB/s
     http_req_blocked...............: avg=1.35µs   min=832ns    med=1.14µs   max=162.52µs p(90)=1.83µs   p(95)=1.98µs  
     http_req_connecting............: avg=0ns      min=0s       med=0s       max=109.19µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=133.3µs  min=90.72µs  med=129.16µs max=6.88ms   p(90)=150.62µs p(95)=157.59µs
       { expected_response:true }...: avg=133.3µs  min=90.72µs  med=129.16µs max=6.88ms   p(90)=150.62µs p(95)=157.59µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 153169
     http_req_receiving.............: avg=24.5µs   min=11.86µs  med=22.87µs  max=2.83ms   p(90)=30.14µs  p(95)=33.17µs 
     http_req_sending...............: avg=6.26µs   min=4µs      med=5.39µs   max=324µs    p(90)=8µs      p(95)=8.79µs  
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=102.52µs min=63.56µs  med=97.95µs  max=6.78ms   p(90)=116.32µs p(95)=122.18µs
     http_reqs......................: 153169  5105.436824/s
     iteration_duration.............: avg=191.38µs min=137.42µs med=186.45µs max=7.02ms   p(90)=211.63µs p(95)=220.94µs
     iterations.....................: 153169  5105.436824/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Contributor

github-actions bot commented May 13, 2025

@benchmarks/server results (undici)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 219362      ✗ 0     
     data_received..................: 22 MB   728 kB/s
     data_sent......................: 8.8 MB  293 kB/s
     http_req_blocked...............: avg=1.42µs   min=892ns    med=1.18µs   max=199.18µs p(90)=1.9µs    p(95)=2.06µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=134.7µs  p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=211.45µs min=150.85µs med=200.77µs max=74.13ms  p(90)=226.51µs p(95)=235.94µs
       { expected_response:true }...: avg=211.45µs min=150.85µs med=200.77µs max=74.13ms  p(90)=226.51µs p(95)=235.94µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 109681
     http_req_receiving.............: avg=25.64µs  min=13.64µs  med=23.97µs  max=2.8ms    p(90)=30.99µs  p(95)=33.79µs 
     http_req_sending...............: avg=6.28µs   min=3.99µs   med=5.44µs   max=299.53µs p(90)=7.99µs   p(95)=8.8µs   
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=179.51µs min=130.27µs med=169.39µs max=74.05ms  p(90)=191.99µs p(95)=200.53µs
     http_reqs......................: 109681  3655.897936/s
     iteration_duration.............: avg=269.05µs min=191.65µs med=257.28µs max=74.29ms  p(90)=286.71µs p(95)=298.44µs
     iterations.....................: 109681  3655.897936/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

Copy link
Contributor

github-actions bot commented May 13, 2025

@benchmarks/server results (native)

     ✓ no-errors
     ✓ expected-result

   ✓ checks.........................: 100.00% ✓ 226206      ✗ 0     
     data_received..................: 23 MB   750 kB/s
     data_sent......................: 9.0 MB  302 kB/s
     http_req_blocked...............: avg=1.6µs    min=942ns    med=1.44µs   max=2.23ms   p(90)=2.02µs   p(95)=2.23µs  
     http_req_connecting............: avg=1ns      min=0s       med=0s       max=119.38µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=198.24µs min=139.22µs med=187.87µs max=89.88ms  p(90)=214.53µs p(95)=225.31µs
       { expected_response:true }...: avg=198.24µs min=139.22µs med=187.87µs max=89.88ms  p(90)=214.53µs p(95)=225.31µs
     http_req_failed................: 0.00%   ✓ 0           ✗ 113103
     http_req_receiving.............: avg=27.45µs  min=14.72µs  med=26.4µs   max=3.08ms   p(90)=33.52µs  p(95)=35.94µs 
     http_req_sending...............: avg=7.16µs   min=4.18µs   med=7.01µs   max=262.94µs p(90)=8.75µs   p(95)=10.22µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=163.62µs min=110.71µs med=154.02µs max=89.8ms   p(90)=177.59µs p(95)=187.46µs
     http_reqs......................: 113103  3769.918545/s
     iteration_duration.............: avg=260.37µs min=192.04µs med=248.69µs max=92.18ms  p(90)=279.4µs  p(95)=293.01µs
     iterations.....................: 113103  3769.918545/s
     vus............................: 1       min=1         max=1   
     vus_max........................: 1       min=1         max=1   

@ardatan ardatan changed the title enhance(node-fetch): avoid creating a new AbortSignal which is expensive Performance optimizations May 15, 2025
@ardatan ardatan marked this pull request as ready for review May 15, 2025 13:36
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/node-fetch/src/utils.ts (1)

89-91: Consider potential edge cases with custom isArray implementation.

This new utility checks for array characteristics but might return false positives for array-like objects (NodeList, arguments, objects with these properties). While likely more performant than Array.isArray(), ensure it's only used with inputs where these edge cases won't occur.

If you need strict array checking, consider:

export function isArray<T>(value: any): value is T[] {
-  return value?.length && value?.map && value?.slice && value?.splice;
+  return value?.length !== undefined && typeof value?.map === 'function' && 
+         typeof value?.slice === 'function' && typeof value?.splice === 'function';
}

Or to eliminate all edge cases:

export function isArray<T>(value: any): value is T[] {
-  return value?.length && value?.map && value?.slice && value?.splice;
+  return Array.isArray(value);
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68f3361 and f6d1c26.

📒 Files selected for processing (13)
  • .changeset/curvy-seas-smoke.md (1 hunks)
  • .changeset/stupid-candies-fetch.md (0 hunks)
  • .github/workflows/benchmarks.yml (0 hunks)
  • packages/node-fetch/src/Body.ts (5 hunks)
  • packages/node-fetch/src/Headers.ts (8 hunks)
  • packages/node-fetch/src/Request.ts (1 hunks)
  • packages/node-fetch/src/Response.ts (2 hunks)
  • packages/node-fetch/src/fetchCurl.ts (3 hunks)
  • packages/node-fetch/src/fetchNodeHttp.ts (3 hunks)
  • packages/node-fetch/src/utils.ts (1 hunks)
  • packages/server/src/createServerAdapter.ts (2 hunks)
  • packages/server/src/plugins/useCors.ts (2 hunks)
  • packages/server/src/utils.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • .github/workflows/benchmarks.yml
  • .changeset/stupid-candies-fetch.md
🧰 Additional context used
🧬 Code Graph Analysis (6)
packages/server/src/plugins/useCors.ts (1)
packages/server/src/utils.ts (1)
  • isArray (645-647)
packages/node-fetch/src/fetchCurl.ts (1)
packages/node-fetch/src/Request.ts (1)
  • signal (113-116)
packages/node-fetch/src/utils.ts (1)
packages/server/src/utils.ts (1)
  • isArray (645-647)
packages/node-fetch/src/Response.ts (2)
packages/node-fetch/src/Headers.ts (1)
  • isHeadersLike (7-9)
packages/node-fetch/src/utils.ts (1)
  • isArray (89-91)
packages/node-fetch/src/Headers.ts (1)
packages/node-fetch/src/utils.ts (1)
  • isArray (89-91)
packages/node-fetch/src/Body.ts (2)
packages/node-fetch/src/Blob.ts (3)
  • PonyfillBlob (72-321)
  • PonyfillBlob (323-326)
  • text (193-217)
packages/promise-helpers/src/index.ts (1)
  • fakePromise (47-94)
🔇 Additional comments (20)
packages/server/src/plugins/useCors.ts (1)

2-2: Import change aligns with optimization pattern.

Replacing Array.isArray() with the custom isArray() utility is consistent with the optimization approach used elsewhere in the codebase.

Also applies to: 50-50

packages/node-fetch/src/fetchCurl.ts (2)

38-45: Well-optimized abort signal handling.

This approach avoids unnecessary creation of AbortController objects by prioritizing _signal when available. The code properly handles the null case and only falls back to the getter property which might create a new signal as a last resort.


48-48: Signal reference updates for consistency.

All references to fetchRequest.signal are properly updated to use the new local signal variable, ensuring consistent signal handling throughout the function.

Also applies to: 107-107, 114-114, 136-136

.changeset/curvy-seas-smoke.md (1)

1-14: Clear and comprehensive changeset documentation.

The changeset clearly documents all performance optimizations being implemented. This provides excellent context for reviewers and future maintainers.

packages/node-fetch/src/Request.ts (1)

112-116: Great performance optimization with lazy signal initialization

This change improves performance by lazily initializing the abort signal only when needed, rather than eagerly creating it in the constructor. This avoids the expensive creation of AbortController and AbortSignal objects when they're not actually used.

- signal: AbortSignal = new AbortController().signal; // or similar eager initialization
+ get signal(): AbortSignal {
+   this._signal ||= new AbortController().signal;
+   return this._signal;
+ }
packages/server/src/createServerAdapter.ts (2)

4-4: New import for promise utilities

Adding unfakePromise and MaybePromise to support the refactored promise chain below.


299-313: Improved async control flow with promise chaining

This refactoring improves code readability by replacing nested callback-style handleMaybePromise calls with a cleaner promise chain. The new approach maintains the same logic flow while making the sequence of operations more explicit.

packages/node-fetch/src/fetchNodeHttp.ts (2)

43-51: Centralized abort signal management

Good improvement to centralize the logic for selecting which abort signal to use. This supports the lazy initialization pattern introduced in the Request.ts file and makes the code more maintainable by reducing duplication.


60-61: Consistent signal usage across all call sites

These changes correctly update all call sites to use the centralized signal variable instead of directly accessing fetchRequest.signal. This ensures consistent behavior across the entire fetch implementation.

Also applies to: 67-68, 123-124, 138-139, 145-146

packages/node-fetch/src/Headers.ts (1)

35-35: Performance optimization for Headers manipulation

This optimization prevents unnecessary map creation by directly manipulating the original headersInit data structure when setting header values. The code handles different types of header initialization objects (arrays, Headers-like objects, and plain objects) appropriately.

This aligns with the PR objective of "Preventing the creation of a new map for Headers by attempting to reuse the initialization object for Headers" and will improve performance in scenarios where a single Headers object is reused.

Also applies to: 76-76, 155-169, 189-189, 218-218, 243-243

packages/node-fetch/src/Response.ts (2)

4-4: Good addition of utility import.

The import of isArray from ./utils.js follows the pattern of using utility functions for type checking, which is consistent with the codebase's approach.


68-91: Excellent header handling optimization in json method.

This refactoring improves performance by handling different header formats more efficiently. The key optimizations are:

  1. Making init parameter optional rather than creating an empty object by default
  2. Handling multiple header formats (objects, arrays, Headers-like objects) with type-specific logic
  3. Only modifying headers when the content-type isn't already set

This change aligns with the PR objectives of reducing unnecessary object creation and improving efficiency in handling headers.

packages/server/src/utils.ts (3)

318-357: Excellent header optimization strategy.

This implementation significantly improves performance by:

  1. Directly using the headersInit object when possible
  2. Utilizing setHeaders method (available since Node.js 18) for efficient header forwarding
  3. Implementing special handling for set-cookie headers which often require array support

These optimizations align perfectly with the PR objectives of preventing creation of new maps for Headers and utilizing setHeaders when available.


359-366: Great optimization for string body handling.

This addition avoids unnecessary buffer creation for string bodies by writing them directly to the response stream. This is an excellent performance improvement, especially for API responses that frequently return JSON or text content.


645-647: Good utility function implementation.

The isArray utility function uses feature detection rather than Array.isArray to check for array-like objects. This is consistent with the optimization approach in the PR and allows for more flexible handling of array-like structures.

packages/node-fetch/src/Body.ts (5)

48-48: Good refactoring of signal handling.

Changing the signal property from public to private _signal aligns with best practices for encapsulation. Making it optional in the interface allows for more flexible usage while maintaining compatibility.

Also applies to: 55-55, 61-61


202-208: Excellent optimization for string body handling in blob method.

This enhancement efficiently handles string body types by:

  1. Setting the text directly without unnecessary conversions
  2. Creating a PonyfillBlob with the correct type and size
  3. Avoiding the overhead of buffer creation until actually needed

This change aligns with the PR objectives of improving performance by avoiding unnecessary object creation.


359-369: Improved buffer handling for string bodies.

This optimization avoids redundant processing when text content is already available, either directly or via the bodyType. The implementation efficiently creates buffers only when needed, reducing memory usage and improving performance.


468-478: Excellent optimization for string body processing.

The changes to processBodyInit improve performance by:

  1. Computing the content length directly using Buffer.byteLength
  2. Creating buffers on-demand in the bodyFactory function rather than eagerly
  3. Using Readable.from with a buffer created only when needed

This deferred buffer creation strategy is a key performance optimization, especially for string bodies which are common in web applications.


275-276: Signal handling properly updated in form data processing.

The reference to _signal is correctly updated in the form data parsing logic, maintaining the functionality while using the new private property.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/server/src/utils.ts (1)

650-652: Consider using additional checks for arrays.

The isArray utility function checks multiple array properties, but it might have false positives for array-like objects that aren't actual arrays.

Consider adding more specific checks or using the native Array.isArray when possible:

export function isArray<T>(value: any): value is T[] {
-  return value?.length != null && value?.map && value?.slice && value?.splice;
+  return value?.length != null && typeof value?.map === 'function' && 
+         typeof value?.slice === 'function' && typeof value?.splice === 'function';
}
packages/server/test/server.bench.ts (2)

26-49: Effective comparative benchmarking setup.

This benchmark effectively compares the performance between the native Node.js HTTP server and the whatwg-node/server adapter by:

  1. Setting up equivalent servers with the same functionality
  2. Using dynamic port allocation for both servers (good practice)
  3. Making equivalent requests to both servers
  4. Using consistent benchmark configuration

This will provide valuable performance data to validate the optimizations mentioned in the PR description.

Consider adding cleanup code to close the servers after benchmarks complete:

describe('Simple JSON Response', () => {
  const simpleNodeServer = createServer((_req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ hello: 'world' }));
  });
  const simpleNodePort = (simpleNodeServer.listen(0).address() as AddressInfo).port;
+ 
+ // Clean up server after tests
+ afterAll(() => {
+   simpleNodeServer.close();
+ });
  
  bench(
    'node:http',
    () => fetch(`http://localhost:${simpleNodePort}`).then(res => res.json()),
    benchConfig,
  );

  const whatwgNodeServer = createServer(createServerAdapter(() => Response.json({ hello: 'world' })));
  const whatwgNodePort = (whatwgNodeServer.listen(0).address() as AddressInfo).port;
+
+ // Clean up server after tests
+ afterAll(() => {
+   whatwgNodeServer.close();
+ });
  
  bench(
    '@whatwg-node/server',
    () => fetch(`http://localhost:${whatwgNodePort}`).then(res => res.json()),
    benchConfig,
  );
});

38-42: Consider refactoring server setup for better readability.

The whatwg-node server setup combines server creation, listening, and port extraction in a single complex expression.

Refactor to match the style of the native HTTP server setup for improved readability:

- const whatwgNodeServer = (
-   createServer(createServerAdapter(() => Response.json({ hello: 'world' })))
-     .listen(0)
-     .address() as AddressInfo
- ).port;
+ const whatwgNodeServer = createServer(createServerAdapter(() => Response.json({ hello: 'world' })));
+ const whatwgNodePort = (whatwgNodeServer.listen(0).address() as AddressInfo).port;

Then update the fetch URL to use whatwgNodePort instead of whatwgNodeServer.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce23c71 and 4114d74.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (8)
  • package.json (2 hunks)
  • packages/server/src/createServerAdapter.ts (4 hunks)
  • packages/server/src/utils.ts (3 hunks)
  • packages/server/test/server.bench.ts (1 hunks)
  • tsconfig.build.json (1 hunks)
  • tsconfig.json (1 hunks)
  • vitest.config.ts (1 hunks)
  • vitest.projects.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • tsconfig.json
  • tsconfig.build.json
  • vitest.projects.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/createServerAdapter.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/server/src/utils.ts (3)
packages/node-fetch/src/utils.ts (2)
  • isArray (94-96)
  • safeWrite (85-92)
packages/promise-helpers/src/index.ts (1)
  • handleMaybePromise (29-42)
packages/node-fetch/src/AbortError.ts (1)
  • reason (13-15)
🪛 GitHub Check: lint
vitest.config.ts

[failure] 1-1:
'vite-tsconfig-paths' should be listed in the project's dependencies, not devDependencies

⏰ Context from checks skipped due to timeout of 90000ms (19)
  • GitHub Check: unit / node 24
  • GitHub Check: unit / node 23
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
  • GitHub Check: e2e / cloudflare-modules
  • GitHub Check: e2e / cloudflare-workers
  • GitHub Check: server (undici)
  • GitHub Check: server (ponyfill)
  • GitHub Check: prettier
  • GitHub Check: server (native)
  • GitHub Check: alpha / snapshot
  • GitHub Check: e2e / azure-function
  • GitHub Check: lint
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: esm
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: e2e / aws-lambda
🔇 Additional comments (8)
packages/server/src/utils.ts (4)

118-121: Performance optimization for AbortController creation.

This change conditionally creates either a native AbortController or a custom implementation based on the Request implementation. This is a good optimization that avoids creating unnecessary AbortController/AbortSignal objects when they're not needed.


321-362: Efficient header handling optimization.

This optimization intelligently reuses the existing headers object when possible instead of creating a new map. The implementation properly handles special cases:

  1. Direct use of writeHead with the raw headersInit object when safe
  2. Using setHeaders method when available in Node.js 18+
  3. Falling back to manual header iteration with special handling for set-cookie headers

This aligns with the PR objective to prevent creating a new map for Headers objects, improving performance for header-heavy applications.


364-371: Optimized string body handling.

This new branch efficiently handles string response bodies without creating unnecessary Buffer objects, which is a good performance optimization for text responses. The implementation leverages the handleMaybePromise helper for proper asynchronous flow control.


654-691: Well-designed custom AbortController implementation.

The CustomAbortControllerSignal class is well-implemented, extending EventTarget and properly implementing both AbortSignal and AbortController interfaces. The class correctly:

  1. Tracks abort state and reason
  2. Implements throwIfAborted method
  3. Provides a self-referential signal getter
  4. Properly manages event listeners for the onabort handler

This implementation supports the conditional AbortController creation strategy and allows for performance optimizations.

vitest.config.ts (1)

1-6:

✅ Verification successful

Good implementation of Vitest configuration.

The configuration is well-structured and correctly imports the necessary dependencies to enable TypeScript path resolution for Vitest tests. This will ensure that TypeScript path aliases defined in tsconfig.json work correctly during testing.


🏁 Script executed:

#!/bin/bash
# Check if vite-tsconfig-paths is listed in dependencies or devDependencies in package.json
grep -E '"vite-tsconfig-paths":' package.json

Length of output: 83


Vitest configuration verified

  • Confirmed that “vite-tsconfig-paths” is declared in package.json (version 5.1.4).
  • No further changes needed—configuration is correct.
🧰 Tools
🪛 GitHub Check: lint

[failure] 1-1:
'vite-tsconfig-paths' should be listed in the project's dependencies, not devDependencies

package.json (2)

11-11: Good addition of bench script.

Adding a benchmark script to run performance tests is a good practice for monitoring and improving code performance, which aligns well with the PR's performance optimization objectives.


73-75: Dependencies added properly to support benchmarking.

The added dependencies (updated TypeScript, vite-tsconfig-paths, and vitest) properly support the benchmarking infrastructure being introduced.

packages/server/test/server.bench.ts (1)

6-25: Well-structured benchmark configuration with environment awareness.

The benchmark configuration is well-designed with:

  • CI environment detection
  • Configurable parameters via environment variables
  • Reasonable defaults for both CI and local environments
  • Helper function to parse numeric environment variables

This approach ensures the benchmarks will run appropriately in both local development and CI environments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/server/test/reproductions.spec.ts (1)

97-113: Improved error handling for JSON parsing failures.

The changes introduce proper error handling for the JSON parsing process in the test server adapter. This is a good improvement as it:

  1. Prevents unhandled promise rejections when malformed JSON is encountered
  2. Provides structured error responses that include detailed error information
  3. Uses cleaner promise chain formatting for better readability

Consider masking stack traces in error responses even in tests, as it's a good practice to avoid exposing detailed implementation information:

  .catch(error =>
    Response.json({
      error: {
        name: error.name,
        message: error.message,
-       stack: error.stack,
+       // Removed stack for security best practices
      },
    }),
  ),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4114d74 and 057b4da.

📒 Files selected for processing (2)
  • packages/server/src/utils.ts (3 hunks)
  • packages/server/test/reproductions.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/server/src/utils.ts
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: e2e / azure-function
  • GitHub Check: e2e / aws-lambda
  • GitHub Check: unit / deno
  • GitHub Check: unit / node 24
  • GitHub Check: unit / node 23
  • GitHub Check: unit / node 20
  • GitHub Check: unit / bun
  • GitHub Check: unit / node 18
  • GitHub Check: server (undici)
  • GitHub Check: node-fetch (consumeBody)
  • GitHub Check: server (ponyfill)
  • GitHub Check: node-fetch (noConsumeBody)
  • GitHub Check: server (native)

@ardatan ardatan force-pushed the avoid-signal branch 4 times, most recently from a77e698 to 1ce5f83 Compare May 16, 2025 23:09
@enisdenjo enisdenjo merged commit 28c4ad9 into master May 20, 2025
30 checks passed
@enisdenjo enisdenjo deleted the avoid-signal branch May 20, 2025 14:30
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.

2 participants