-
Notifications
You must be signed in to change notification settings - Fork 30.2k
feat(experimental): option to polyfill fetch using undici in Node.js <18
#40318
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
Changes from 33 commits
66408d8
28a09b8
0daf850
1c5c027
f4169d7
0b2475a
07be524
32d3677
4cbc65f
eb44e6b
7fd115a
2a962a4
6593733
a11d117
ebc33d7
f623f65
502db7f
88c65cd
1651916
8393aaf
76d7b6c
367862f
3866700
58ecb4c
454d917
1b26d54
0de1fb4
ec21630
e8550fa
7e6b1f9
4919546
ef8b44f
789b43b
245ff7b
60d99d7
0de3c26
989f9c8
ed03b2b
421891b
acf39cb
203c3e7
bbc2ac5
cbafb7f
d2087ce
f770bd5
31beccc
7722ea5
424531a
7da8b81
72c90fe
e5f3c60
fbc3e4d
fcfa989
08fefca
b101ec6
82a0961
ef18f28
05ca18f
519c717
6002dba
8ed03ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| MIT License | ||
|
|
||
| Copyright (c) Matteo Collina and Undici contributors | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| {"name":"undici","main":"index.js","author":"Matteo Collina <[email protected]>","license":"MIT"} |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import { | |
| ExperimentalConfig, | ||
| NextConfigComplete, | ||
| validateConfig, | ||
| NextConfig, | ||
| } from './config-shared' | ||
| import { loadWebpackHook } from './config-utils' | ||
| import { | ||
|
|
@@ -45,9 +46,8 @@ const experimentalWarning = execOnce( | |
| } | ||
| ) | ||
|
|
||
| export function setHttpAgentOptions( | ||
| options: NextConfigComplete['httpAgentOptions'] | ||
| ) { | ||
| export function setHttpClientAndAgentOptions(options: NextConfig) { | ||
| ;(global as any).__NEXT_USE_UNDICI = options.experimental?.useUndici | ||
|
||
| if ((global as any).__NEXT_HTTP_AGENT) { | ||
| // We only need to assign once because we want | ||
| // to resuse the same agent for all requests. | ||
|
|
@@ -58,8 +58,9 @@ export function setHttpAgentOptions( | |
| throw new Error('Expected config.httpAgentOptions to be an object') | ||
| } | ||
|
|
||
| ;(global as any).__NEXT_HTTP_AGENT = new HttpAgent(options) | ||
| ;(global as any).__NEXT_HTTPS_AGENT = new HttpsAgent(options) | ||
| ;(global as any).__NEXT_HTTP_AGENT_OPTIONS = options.httpAgentOptions | ||
| ;(global as any).__NEXT_HTTP_AGENT = new HttpAgent(options.httpAgentOptions) | ||
| ;(global as any).__NEXT_HTTPS_AGENT = new HttpsAgent(options.httpAgentOptions) | ||
| } | ||
|
|
||
| function assignDefaults(userConfig: { [key: string]: any }) { | ||
|
|
@@ -545,9 +546,7 @@ function assignDefaults(userConfig: { [key: string]: any }) { | |
|
|
||
| // TODO: Change defaultConfig type to NextConfigComplete | ||
| // so we don't need "!" here. | ||
| setHttpAgentOptions( | ||
| result.httpAgentOptions || defaultConfig.httpAgentOptions! | ||
| ) | ||
| setHttpClientAndAgentOptions(result || defaultConfig) | ||
|
|
||
| if (result.i18n) { | ||
| const { i18n } = result | ||
|
|
@@ -865,6 +864,6 @@ export default async function loadConfig( | |
| // reactRoot can be updated correctly even with no next.config.js | ||
| const completeConfig = assignDefaults(defaultConfig) as NextConfigComplete | ||
| completeConfig.configFileName = configFileName | ||
| setHttpAgentOptions(completeConfig.httpAgentOptions) | ||
| setHttpClientAndAgentOptions(completeConfig) | ||
| return completeConfig | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,56 @@ | ||
| import fetch, { | ||
| Headers, | ||
| Request, | ||
| Response, | ||
| } from 'next/dist/compiled/node-fetch' | ||
|
|
||
| // Polyfill fetch() in the Node.js environment | ||
|
|
||
| if (!global.fetch) { | ||
| const agent = ({ protocol }) => | ||
| protocol === 'http:' ? global.__NEXT_HTTP_AGENT : global.__NEXT_HTTPS_AGENT | ||
| const fetchWithAgent = (url, opts, ...rest) => { | ||
| if (!opts) { | ||
| opts = { agent } | ||
| } else if (!opts.agent) { | ||
| opts.agent = agent | ||
| function getFetchImpl() { | ||
| return global.__NEXT_USE_UNDICI | ||
| ? require('next/dist/compiled/undici') | ||
| : require('next/dist/compiled/node-fetch') | ||
| } | ||
| // Due to limitation of global configuartion, we have to do this resolution at runtime | ||
Ethan-Arrowood marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| global.fetch = (...args) => { | ||
| const fetchImpl = getFetchImpl() | ||
|
|
||
| if (global.__NEXT_USE_UNDICI) { | ||
| // Undici does not support the `keepAlive` option, | ||
| // instead we have to pass a custom dispatcher | ||
| if ( | ||
| !global.__NEXT_HTTP_AGENT_OPTIONS.keepAlive && | ||
balazsorban44 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| !global.__NEXT_UNDICI_AGENT_SET | ||
| ) { | ||
| global.__NEXT_UNDICI_AGENT_SET = true | ||
| fetchImpl.setGlobalDispatcher(new fetchImpl.Agent({ pipelining: 0 })) | ||
| } | ||
| return fetchImpl.fetch(...args) | ||
| } | ||
| return fetch(url, opts, ...rest) | ||
| const agent = ({ protocol }) => | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be a problem because the agent is created for every
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, the Agent is created and assigned to But since we can't do that for the Undici agent we have to have all that |
||
| protocol === 'http:' | ||
| ? global.__NEXT_HTTP_AGENT | ||
| : global.__NEXT_HTTPS_AGENT | ||
|
|
||
| if (!args[1]) { | ||
| args[1] = { agent } | ||
| } else if (!args[1].agent) { | ||
| args[1].agent = agent | ||
| } | ||
|
|
||
| return fetchImpl(...args) | ||
| } | ||
| global.fetch = fetchWithAgent | ||
| global.Headers = Headers | ||
| global.Request = Request | ||
| global.Response = Response | ||
|
|
||
| Object.defineProperties(global, { | ||
| Headers: { | ||
| get() { | ||
| return getFetchImpl().Headers | ||
| }, | ||
| }, | ||
| Request: { | ||
| get() { | ||
| return getFetchImpl().Request | ||
| }, | ||
| }, | ||
| Response: { | ||
| get() { | ||
| return getFetchImpl().Response | ||
| }, | ||
| }, | ||
| }) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.