From 730c812d051f6a2f3887ef543cfa750e204e9204 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 19 Jun 2025 18:41:19 +0200 Subject: [PATCH 1/2] feat: add install() function for global WHATWG fetch classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a top-level install() export that installs all WHATWG fetch classes on globalThis, making them available globally without imports. - Add install() function to index.js that sets globalThis properties - Include fetch, Headers, Response, Request, FormData, WebSocket, events, and EventSource - Add comprehensive test coverage for the install function - Document the feature in README.md with usage examples - Add API documentation for the docs website This is useful for polyfilling environments, ensuring consistent fetch behavior across Node.js versions, and making undici's implementations available globally for libraries that expect them. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Signed-off-by: Matteo Collina --- README.md | 38 +++++++++ docs/docs/api/GlobalInstallation.md | 91 ++++++++++++++++++++ docs/docsify/sidebar.md | 1 + index.js | 13 +++ test/install.js | 126 ++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 docs/docs/api/GlobalInstallation.md create mode 100644 test/install.js diff --git a/README.md b/README.md index 565e002355f..966f2be95ec 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,44 @@ for await (const data of body) { console.log('data', data) } console.log('trailers', trailers) ``` +## Global Installation + +Undici provides an `install()` function to add all WHATWG fetch classes to `globalThis`, making them available globally: + +```js +import { install } from 'undici' + +// Install all WHATWG fetch classes globally +install() + +// Now you can use fetch classes globally without importing +const response = await fetch('https://api.example.com/data') +const data = await response.json() + +// All classes are available globally: +const headers = new Headers([['content-type', 'application/json']]) +const request = new Request('https://example.com') +const formData = new FormData() +const ws = new WebSocket('wss://example.com') +const eventSource = new EventSource('https://example.com/events') +``` + +The `install()` function adds the following classes to `globalThis`: + +- `fetch` - The fetch function +- `Headers` - HTTP headers management +- `Response` - HTTP response representation +- `Request` - HTTP request representation +- `FormData` - Form data handling +- `WebSocket` - WebSocket client +- `CloseEvent`, `ErrorEvent`, `MessageEvent` - WebSocket events +- `EventSource` - Server-sent events client + +This is useful for: +- Polyfilling environments that don't have fetch +- Ensuring consistent fetch behavior across different Node.js versions +- Making undici's implementations available globally for libraries that expect them + ## Body Mixins The `body` mixins are the most common way to format the request/response body. Mixins include: diff --git a/docs/docs/api/GlobalInstallation.md b/docs/docs/api/GlobalInstallation.md new file mode 100644 index 00000000000..7e4529d8f19 --- /dev/null +++ b/docs/docs/api/GlobalInstallation.md @@ -0,0 +1,91 @@ +# Global Installation + +Undici provides an `install()` function to add all WHATWG fetch classes to `globalThis`, making them available globally without requiring imports. + +## `install()` + +Install all WHATWG fetch classes globally on `globalThis`. + +**Example:** + +```js +import { install } from 'undici' + +// Install all WHATWG fetch classes globally +install() + +// Now you can use fetch classes globally without importing +const response = await fetch('https://api.example.com/data') +const data = await response.json() + +// All classes are available globally: +const headers = new Headers([['content-type', 'application/json']]) +const request = new Request('https://example.com') +const formData = new FormData() +const ws = new WebSocket('wss://example.com') +const eventSource = new EventSource('https://example.com/events') +``` + +## Installed Classes + +The `install()` function adds the following classes to `globalThis`: + +| Class | Description | +|-------|-------------| +| `fetch` | The fetch function for making HTTP requests | +| `Headers` | HTTP headers management | +| `Response` | HTTP response representation | +| `Request` | HTTP request representation | +| `FormData` | Form data handling | +| `WebSocket` | WebSocket client | +| `CloseEvent` | WebSocket close event | +| `ErrorEvent` | WebSocket error event | +| `MessageEvent` | WebSocket message event | +| `EventSource` | Server-sent events client | + +## Use Cases + +Global installation is useful for: + +- **Polyfilling environments** that don't have native fetch support +- **Ensuring consistent behavior** across different Node.js versions +- **Library compatibility** when third-party libraries expect global fetch +- **Migration scenarios** where you want to replace built-in implementations +- **Testing environments** where you need predictable fetch behavior + +## Example: Polyfilling an Environment + +```js +import { install } from 'undici' + +// Check if fetch is available and install if needed +if (typeof globalThis.fetch === 'undefined') { + install() + console.log('Undici fetch installed globally') +} + +// Now fetch is guaranteed to be available +const response = await fetch('https://api.example.com') +``` + +## Example: Testing Environment + +```js +import { install } from 'undici' + +// In test setup, ensure consistent fetch behavior +install() + +// Now all tests use undici's implementations +test('fetch API test', async () => { + const response = await fetch('https://example.com') + expect(response).toBeInstanceOf(Response) +}) +``` + +## Notes + +- The `install()` function overwrites any existing global implementations +- Classes installed are undici's implementations, not Node.js built-ins +- This provides access to undici's latest features and performance improvements +- The global installation persists for the lifetime of the process \ No newline at end of file diff --git a/docs/docsify/sidebar.md b/docs/docsify/sidebar.md index efbc217f33d..6179a95c87b 100644 --- a/docs/docsify/sidebar.md +++ b/docs/docsify/sidebar.md @@ -14,6 +14,7 @@ * [Errors](/docs/api/Errors.md "Undici API - Errors") * [EventSource](/docs/api/EventSource.md "Undici API - EventSource") * [Fetch](/docs/api/Fetch.md "Undici API - Fetch") + * [Global Installation](/docs/api/GlobalInstallation.md "Undici API - Global Installation") * [Cookies](/docs/api/Cookies.md "Undici API - Cookies") * [MockClient](/docs/api/MockClient.md "Undici API - MockClient") * [MockPool](/docs/api/MockPool.md "Undici API - MockPool") diff --git a/index.js b/index.js index 625ec98f0ef..4d6e6a355f2 100644 --- a/index.js +++ b/index.js @@ -181,3 +181,16 @@ module.exports.mockErrors = mockErrors const { EventSource } = require('./lib/web/eventsource/eventsource') module.exports.EventSource = EventSource + +module.exports.install = function install () { + globalThis.fetch = module.exports.fetch + globalThis.Headers = module.exports.Headers + globalThis.Response = module.exports.Response + globalThis.Request = module.exports.Request + globalThis.FormData = module.exports.FormData + globalThis.WebSocket = module.exports.WebSocket + globalThis.CloseEvent = module.exports.CloseEvent + globalThis.ErrorEvent = module.exports.ErrorEvent + globalThis.MessageEvent = module.exports.MessageEvent + globalThis.EventSource = module.exports.EventSource +} diff --git a/test/install.js b/test/install.js new file mode 100644 index 00000000000..f09dc0f97de --- /dev/null +++ b/test/install.js @@ -0,0 +1,126 @@ +'use strict' + +const { test } = require('node:test') +const assert = require('node:assert') +const { install } = require('../index') + +test('install() should add WHATWG fetch classes to globalThis', () => { + // Save original globals to restore later + const originalFetch = globalThis.fetch + const originalHeaders = globalThis.Headers + const originalResponse = globalThis.Response + const originalRequest = globalThis.Request + const originalFormData = globalThis.FormData + const originalWebSocket = globalThis.WebSocket + const originalCloseEvent = globalThis.CloseEvent + const originalErrorEvent = globalThis.ErrorEvent + const originalMessageEvent = globalThis.MessageEvent + const originalEventSource = globalThis.EventSource + + try { + // Remove any existing globals + delete globalThis.fetch + delete globalThis.Headers + delete globalThis.Response + delete globalThis.Request + delete globalThis.FormData + delete globalThis.WebSocket + delete globalThis.CloseEvent + delete globalThis.ErrorEvent + delete globalThis.MessageEvent + delete globalThis.EventSource + + // Verify they're not defined + assert.strictEqual(globalThis.fetch, undefined) + assert.strictEqual(globalThis.Headers, undefined) + assert.strictEqual(globalThis.Response, undefined) + assert.strictEqual(globalThis.Request, undefined) + assert.strictEqual(globalThis.FormData, undefined) + assert.strictEqual(globalThis.WebSocket, undefined) + assert.strictEqual(globalThis.CloseEvent, undefined) + assert.strictEqual(globalThis.ErrorEvent, undefined) + assert.strictEqual(globalThis.MessageEvent, undefined) + assert.strictEqual(globalThis.EventSource, undefined) + + // Call install() + install() + + // Verify all classes are now installed + assert.strictEqual(typeof globalThis.fetch, 'function') + assert.strictEqual(typeof globalThis.Headers, 'function') + assert.strictEqual(typeof globalThis.Response, 'function') + assert.strictEqual(typeof globalThis.Request, 'function') + assert.strictEqual(typeof globalThis.FormData, 'function') + assert.strictEqual(typeof globalThis.WebSocket, 'function') + assert.strictEqual(typeof globalThis.CloseEvent, 'function') + assert.strictEqual(typeof globalThis.ErrorEvent, 'function') + assert.strictEqual(typeof globalThis.MessageEvent, 'function') + assert.strictEqual(typeof globalThis.EventSource, 'function') + + // Test that the installed classes are functional + const headers = new globalThis.Headers([['content-type', 'application/json']]) + assert.strictEqual(headers.get('content-type'), 'application/json') + + const request = new globalThis.Request('https://example.com') + assert.strictEqual(request.url, 'https://example.com/') + + const response = new globalThis.Response('test body') + assert.strictEqual(response.status, 200) + + const formData = new globalThis.FormData() + formData.append('key', 'value') + assert.strictEqual(formData.get('key'), 'value') + } finally { + // Restore original globals + if (originalFetch !== undefined) { + globalThis.fetch = originalFetch + } else { + delete globalThis.fetch + } + if (originalHeaders !== undefined) { + globalThis.Headers = originalHeaders + } else { + delete globalThis.Headers + } + if (originalResponse !== undefined) { + globalThis.Response = originalResponse + } else { + delete globalThis.Response + } + if (originalRequest !== undefined) { + globalThis.Request = originalRequest + } else { + delete globalThis.Request + } + if (originalFormData !== undefined) { + globalThis.FormData = originalFormData + } else { + delete globalThis.FormData + } + if (originalWebSocket !== undefined) { + globalThis.WebSocket = originalWebSocket + } else { + delete globalThis.WebSocket + } + if (originalCloseEvent !== undefined) { + globalThis.CloseEvent = originalCloseEvent + } else { + delete globalThis.CloseEvent + } + if (originalErrorEvent !== undefined) { + globalThis.ErrorEvent = originalErrorEvent + } else { + delete globalThis.ErrorEvent + } + if (originalMessageEvent !== undefined) { + globalThis.MessageEvent = originalMessageEvent + } else { + delete globalThis.MessageEvent + } + if (originalEventSource !== undefined) { + globalThis.EventSource = originalEventSource + } else { + delete globalThis.EventSource + } + } +}) From a4eb23077567af448b7f5c2fb7546b0ed78e4625 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 19 Jun 2025 18:43:04 +0200 Subject: [PATCH 2/2] refactor: make install function a top-level function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the install() function to be declared as a top-level function instead of an anonymous function assigned to module.exports.install. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Signed-off-by: Matteo Collina --- index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4d6e6a355f2..8f2ed39c14e 100644 --- a/index.js +++ b/index.js @@ -182,7 +182,7 @@ const { EventSource } = require('./lib/web/eventsource/eventsource') module.exports.EventSource = EventSource -module.exports.install = function install () { +function install () { globalThis.fetch = module.exports.fetch globalThis.Headers = module.exports.Headers globalThis.Response = module.exports.Response @@ -194,3 +194,5 @@ module.exports.install = function install () { globalThis.MessageEvent = module.exports.MessageEvent globalThis.EventSource = module.exports.EventSource } + +module.exports.install = install