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..8f2ed39c14e 100644 --- a/index.js +++ b/index.js @@ -181,3 +181,18 @@ module.exports.mockErrors = mockErrors const { EventSource } = require('./lib/web/eventsource/eventsource') module.exports.EventSource = EventSource + +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 +} + +module.exports.install = install 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 + } + } +})