Skip to content

Commit

Permalink
remove node fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
feedthejim committed Feb 6, 2025
1 parent 931f9b3 commit dabbab2
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 104 deletions.
66 changes: 12 additions & 54 deletions packages/font/src/google/fetch-css-from-google-fonts.ts
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
// @ts-ignore
import fetch from 'next/dist/compiled/node-fetch'
import { nextFontError } from '../next-font-error'
import { getProxyAgent } from './get-proxy-agent'
import { fetchResource } from './fetch-resource'
import { retry } from './retry'

/**
* Fetches the CSS containing the @font-face declarations from Google Fonts.
* The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
*
* The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
* It's used to define mocked data to avoid hitting the Google Fonts API during tests.
*/
export async function fetchCSSFromGoogleFonts(
url: string,
fontFamily: string,
isDev: boolean
): Promise<string> {
// Check if mocked responses are defined, if so use them instead of fetching from Google Fonts
let mockedResponse: string | undefined
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
const mockFile = require(process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES)
mockedResponse = mockFile[url]
const mockedResponse = mockFile[url]
if (!mockedResponse) {
nextFontError('Missing mocked response for URL: ' + url)
}
return mockedResponse
}

let cssResponse: string
if (mockedResponse) {
// Just use the mocked CSS if it's set
cssResponse = mockedResponse
} else {
// Retry the fetch a few times in case of network issues as some font files
// are quite large:
// https://github.com/vercel/next.js/issues/45080
cssResponse = await retry(async () => {
const controller =
isDev && typeof AbortController !== 'undefined'
? new AbortController()
: undefined
const signal = controller?.signal
const timeoutId = controller
? setTimeout(() => controller.abort(), 3000)
: undefined
const buffer = await retry(async () => {
return fetchResource(
url,
isDev,
`Failed to fetch font \`${fontFamily}\`: ${url}\n` +
`Please check your network connection.`
)
}, 3)

const res = await fetch(url, {
agent: getProxyAgent(),
// Add a timeout in dev
signal,
headers: {
// The file format is based off of the user agent, make sure woff2 files are fetched
'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36',
},
}).finally(() => {
timeoutId && clearTimeout(timeoutId)
})

if (!res.ok) {
nextFontError(
`Failed to fetch font \`${fontFamily}\`.\nURL: ${url}\n\nPlease check if the network is available.`
)
}

return res.text()
}, 3)
}

return cssResponse
return buffer.toString('utf8')
}
30 changes: 10 additions & 20 deletions packages/font/src/google/fetch-font-file.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
// @ts-ignore
import fetch from 'next/dist/compiled/node-fetch'
import { getProxyAgent } from './get-proxy-agent'
import fs from 'node:fs'
import { retry } from './retry'
import { fetchResource } from './fetch-resource'

/**
* Fetch the url and return a buffer with the font file.
* Fetches a font file and returns its contents as a Buffer.
* If NEXT_FONT_GOOGLE_MOCKED_RESPONSES is set, we handle mock data logic.
*/
export async function fetchFontFile(url: string, isDev: boolean) {
// Check if we're using mocked data
if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
// If it's an absolute path, read the file from the filesystem
if (url.startsWith('/')) {
return require('fs').readFileSync(url)
return fs.readFileSync(url)
}
// Otherwise just return a unique buffer
return Buffer.from(url)
}

return await retry(async () => {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 3000)
const arrayBuffer = await fetch(url, {
agent: getProxyAgent(),
// Add a timeout in dev
signal: isDev ? controller.signal : undefined,
})
.then((r: any) => r.arrayBuffer())
.finally(() => {
clearTimeout(timeoutId)
})
return Buffer.from(arrayBuffer)
return fetchResource(
url,
isDev,
`Failed to fetch font file from \`${url}\`.`
)
}, 3)
}
56 changes: 56 additions & 0 deletions packages/font/src/google/fetch-resource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import http from 'node:http'
import https from 'node:https'
import { getProxyAgent } from './get-proxy-agent'

/**
* Makes a simple GET request and returns the entire response as a Buffer.
* - Throws if the response status is not 200.
* - Applies a 3000 ms timeout when `isDev` is `true`.
*/
export function fetchResource(
url: string,
isDev: boolean,
errorMessage?: string
): Promise<Buffer> {
return new Promise((resolve, reject) => {
const { protocol } = new URL(url)
const client = protocol === 'https:' ? https : http
const timeout = isDev ? 3000 : undefined

const req = client.request(
url,
{
agent: getProxyAgent(),
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) ' +
'Chrome/104.0.0.0 Safari/537.36',
},
},
(res) => {
if (res.statusCode !== 200) {
reject(
new Error(
errorMessage ||
`Request failed: ${url} (status: ${res.statusCode})`
)
)
return
}
const chunks: Buffer[] = []
res.on('data', (chunk) => chunks.push(Buffer.from(chunk)))
res.on('end', () => resolve(Buffer.concat(chunks)))
}
)

if (timeout) {
req.setTimeout(timeout, () => {
req.destroy(new Error(`Request timed out after ${timeout}ms`))
})
}

req.on('error', (err) => reject(err))
req.end()
})
}
20 changes: 11 additions & 9 deletions packages/font/src/google/loader.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import nextFontGoogleFontLoader from './loader'
// @ts-ignore
import fetch from 'next/dist/compiled/node-fetch'
import { fetchResource } from './fetch-resource'

jest.mock('next/dist/compiled/node-fetch')
jest.mock('./fetch-resource')

const mockFetchResource = fetchResource as jest.Mock

describe('next/font/google loader', () => {
afterEach(() => {
Expand Down Expand Up @@ -120,10 +121,7 @@ describe('next/font/google loader', () => {
fontFunctionArguments: any,
expectedUrl: any
) => {
fetch.mockResolvedValue({
ok: true,
text: async () => 'OK',
})
mockFetchResource.mockResolvedValue(Buffer.from('OK'))
const { css } = await nextFontGoogleFontLoader({
functionName,
data: [
Expand All @@ -141,8 +139,12 @@ describe('next/font/google loader', () => {
variableName: 'myFont',
})
expect(css).toBe('OK')
expect(fetch).toHaveBeenCalledTimes(1)
expect(fetch).toHaveBeenCalledWith(expectedUrl, expect.any(Object))
expect(mockFetchResource).toHaveBeenCalledTimes(1)
expect(mockFetchResource).toHaveBeenCalledWith(
expectedUrl,
false,
expect.stringContaining('Failed to fetch font')
)
}
)
})
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/next/src/compiled/lru-cache/index.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion packages/next/src/compiled/node-fetch/index.js

This file was deleted.

1 change: 0 additions & 1 deletion packages/next/src/compiled/node-fetch/package.json

This file was deleted.

1 change: 0 additions & 1 deletion packages/next/src/trace/trace-uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import findUp from 'next/dist/compiled/find-up'
import fsPromise from 'fs/promises'
import child_process from 'child_process'
import assert from 'assert'
import fetch from 'next/dist/compiled/node-fetch'
import os from 'os'
import { createInterface } from 'readline'
import { createReadStream } from 'fs'
Expand Down
10 changes: 0 additions & 10 deletions packages/next/taskfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,6 @@ export async function copy_vercel_og(task, opts) {
)
}

// eslint-disable-next-line camelcase
externals['node-fetch'] = 'next/dist/compiled/node-fetch'
export async function ncc_node_fetch(task, opts) {
await task
.source(relative(__dirname, require.resolve('node-fetch')))
.ncc({ packageName: 'node-fetch', externals })
.target('src/compiled/node-fetch')
}

externals['anser'] = 'next/dist/compiled/anser'
externals['next/dist/compiled/anser'] = 'next/dist/compiled/anser'
export async function ncc_node_anser(task, opts) {
Expand Down Expand Up @@ -2309,7 +2300,6 @@ export async function ncc(task, opts) {
'ncc_image_size',
'ncc_hapi_accept',
'ncc_commander',
'ncc_node_fetch',
'ncc_node_anser',
'ncc_node_stacktrace_parser',
'ncc_node_data_uri_to_buffer',
Expand Down
6 changes: 0 additions & 6 deletions packages/next/types/$$compiled.internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,12 +351,6 @@ declare module 'next/dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWe
export = m
}

declare module 'next/dist/compiled/node-fetch' {
import fetch from 'node-fetch'
export * from 'node-fetch'
export default fetch
}

declare module 'next/dist/compiled/commander' {
import commander from 'commander'
export * from 'commander'
Expand Down

0 comments on commit dabbab2

Please sign in to comment.