diff --git a/.changeset/gorgeous-hounds-peel.md b/.changeset/gorgeous-hounds-peel.md
new file mode 100644
index 000000000000..548f4ba3718e
--- /dev/null
+++ b/.changeset/gorgeous-hounds-peel.md
@@ -0,0 +1,8 @@
+---
+'@sveltejs/adapter-netlify': patch
+'@sveltejs/adapter-node': patch
+'@sveltejs/adapter-vercel': patch
+'@sveltejs/kit': patch
+---
+
+Move server-side fetch to adapters instead of build step
diff --git a/examples/hn.svelte.dev/src/routes/[list]/rss.js b/examples/hn.svelte.dev/src/routes/[list]/rss.js
index 9ddf8c730705..77deb67b00cb 100644
--- a/examples/hn.svelte.dev/src/routes/[list]/rss.js
+++ b/examples/hn.svelte.dev/src/routes/[list]/rss.js
@@ -1,5 +1,3 @@
-import fetch from 'node-fetch';
-
const render = (list, items) => `
@@ -11,30 +9,31 @@ const render = (list, items) => `
Svelte HN (${list})
https://hn.svelte.dev/${list}/1
- ${items.map(item => `
- -
- ${item.title}${item.domain ? ` (${item.domain})` : ''}
- https://hn.svelte.dev/item/${item.id}
- link / ` : ''
- }comments
- ]]>
- ${new Date(item.time * 1000).toUTCString()}
-
- `).join('\n')}
+ ${items
+ .map(
+ (item) => `
+ -
+ ${item.title}${item.domain ? ` (${item.domain})` : ''}
+ https://hn.svelte.dev/item/${item.id}
+ link / ` : ''
+ }comments
+ ]]>
+ ${new Date(item.time * 1000).toUTCString()}
+
+ `
+ )
+ .join('\n')}
`;
export function get(req, res) {
- const list = (
- req.params.list === 'top' ? 'news' :
- req.params.list === 'new' ? 'newest' :
- req.params.list
- );
+ const list =
+ req.params.list === 'top' ? 'news' : req.params.list === 'new' ? 'newest' : req.params.list;
fetch(`https://api.hnpwa.com/v0/${list}/1.json`)
- .then(r => r.json())
- .then(items => {
+ .then((r) => r.json())
+ .then((items) => {
const feed = render(list, items);
return {
body: feed,
diff --git a/examples/realworld.svelte.dev/package.json b/examples/realworld.svelte.dev/package.json
index 7263e2afc5d8..21efae26c2d4 100644
--- a/examples/realworld.svelte.dev/package.json
+++ b/examples/realworld.svelte.dev/package.json
@@ -15,7 +15,6 @@
"svelte": "^3.35.0"
},
"dependencies": {
- "cookie": "^0.4.1",
- "node-fetch": "^2.6.1"
+ "cookie": "^0.4.1"
}
}
diff --git a/examples/realworld.svelte.dev/src/lib/api.js b/examples/realworld.svelte.dev/src/lib/api.js
index 58b93be8dbd1..ca753107c676 100644
--- a/examples/realworld.svelte.dev/src/lib/api.js
+++ b/examples/realworld.svelte.dev/src/lib/api.js
@@ -12,11 +12,6 @@ async function send({ method, path, data, token }) {
opts.headers['Authorization'] = `Token ${token}`;
}
- const fetch =
- typeof window !== 'undefined'
- ? window.fetch
- : await import('node-fetch').then((mod) => mod.default);
-
return fetch(`${base}/${path}`, opts)
.then((r) => r.text())
.then((json) => {
diff --git a/examples/realworld.svelte.dev/svelte.config.cjs b/examples/realworld.svelte.dev/svelte.config.cjs
index 4640ea244365..ae50a7e01970 100644
--- a/examples/realworld.svelte.dev/svelte.config.cjs
+++ b/examples/realworld.svelte.dev/svelte.config.cjs
@@ -8,12 +8,6 @@ module.exports = {
adapter: node(),
// hydrate the
element in src/app.html
- target: '#svelte',
-
- vite: {
- ssr: {
- noExternal: ['node-fetch']
- }
- }
+ target: '#svelte'
}
};
diff --git a/packages/adapter-netlify/files/render.js b/packages/adapter-netlify/files/render.js
index d9e90a795432..ccc2a98ea51f 100644
--- a/packages/adapter-netlify/files/render.js
+++ b/packages/adapter-netlify/files/render.js
@@ -2,6 +2,13 @@
import { URLSearchParams } from 'url';
import { render } from './app.mjs'; // eslint-disable-line import/no-unresolved
+import fetch, { Response, Request, Headers } from 'node-fetch';
+
+// provide server-side fetch
+globalThis.fetch = fetch;
+globalThis.Response = Response;
+globalThis.Request = Request;
+globalThis.Headers = Headers;
export async function handler(event) {
const {
diff --git a/packages/adapter-node/src/server.js b/packages/adapter-node/src/server.js
index 8625689d09e7..e144d29d7eaf 100644
--- a/packages/adapter-node/src/server.js
+++ b/packages/adapter-node/src/server.js
@@ -6,6 +6,14 @@ import sirv from 'sirv';
import { URL, fileURLToPath } from 'url';
// eslint-disable-next-line import/no-unresolved
import { get_body } from '@sveltejs/kit/http';
+import fetch, { Response, Request, Headers } from 'node-fetch';
+
+// provide server-side fetch
+globalThis.fetch = fetch;
+globalThis.Response = Response;
+globalThis.Request = Request;
+globalThis.Headers = Headers;
+
// App is a dynamic file built from the application layer.
const __dirname = dirname(fileURLToPath(import.meta.url));
diff --git a/packages/adapter-vercel/src/entry.js b/packages/adapter-vercel/src/entry.js
index 5af07aa6a0b3..72d4158dcf8a 100644
--- a/packages/adapter-vercel/src/entry.js
+++ b/packages/adapter-vercel/src/entry.js
@@ -1,6 +1,13 @@
import { URL } from 'url';
// eslint-disable-next-line import/no-unresolved
import { get_body } from '@sveltejs/kit/http';
+import fetch, { Response, Request, Headers } from 'node-fetch';
+
+// provide server-side fetch
+globalThis.fetch = fetch;
+globalThis.Response = Response;
+globalThis.Request = Request;
+globalThis.Headers = Headers;
export default async (req, res) => {
const { pathname, searchParams } = new URL(req.url || '', 'http://localhost');
diff --git a/packages/kit/src/core/adapt/prerender.js b/packages/kit/src/core/adapt/prerender.js
index daba3491e6c9..8a66dd4c4aff 100644
--- a/packages/kit/src/core/adapt/prerender.js
+++ b/packages/kit/src/core/adapt/prerender.js
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync } from 'fs';
import { dirname, join, resolve as resolve_path } from 'path';
import { parse, pathToFileURL, resolve } from 'url';
import { mkdirp } from '../filesystem/index.js';
+import '../node-fetch-global.js';
/** @param {string} html */
function clean_html(html) {
diff --git a/packages/kit/src/core/dev/index.js b/packages/kit/src/core/dev/index.js
index 6b16cd4ba8fc..bf91715cfe46 100644
--- a/packages/kit/src/core/dev/index.js
+++ b/packages/kit/src/core/dev/index.js
@@ -14,6 +14,7 @@ import { get_body } from '../http/index.js';
import { copy_assets } from '../utils.js';
import svelte from '@sveltejs/vite-plugin-svelte';
import { get_server } from '../server/index.js';
+import '../node-fetch-global.js';
/** @typedef {{ cwd?: string, port: number, host: string, https: boolean, config: import('types/config').ValidatedConfig }} Options */
/** @typedef {import('types/internal').SSRComponent} SSRComponent */
diff --git a/packages/kit/src/core/node-fetch-global.js b/packages/kit/src/core/node-fetch-global.js
new file mode 100644
index 000000000000..d0271ba0000e
--- /dev/null
+++ b/packages/kit/src/core/node-fetch-global.js
@@ -0,0 +1,7 @@
+// @ts-nocheck
+import fetch, { Response, Request, Headers } from 'node-fetch';
+
+globalThis.fetch = fetch;
+globalThis.Response = Response;
+globalThis.Request = Request;
+globalThis.Headers = Headers;
diff --git a/packages/kit/src/core/start/index.js b/packages/kit/src/core/start/index.js
index 01df0f66f704..4cc530f4cf8e 100644
--- a/packages/kit/src/core/start/index.js
+++ b/packages/kit/src/core/start/index.js
@@ -4,6 +4,7 @@ import sirv from 'sirv';
import { get_body } from '../http/index.js';
import { join, resolve } from 'path';
import { get_server } from '../server/index.js';
+import '../node-fetch-global.js';
/** @param {string} dir */
const mutable = (dir) =>
diff --git a/packages/kit/src/runtime/server/page/load_node.js b/packages/kit/src/runtime/server/page/load_node.js
index 6fe2286502eb..c151d31d9bca 100644
--- a/packages/kit/src/runtime/server/page/load_node.js
+++ b/packages/kit/src/runtime/server/page/load_node.js
@@ -1,4 +1,3 @@
-import fetch, { Response } from 'node-fetch';
import { parse, resolve } from 'url';
import { normalize } from '../../load.js';
import { ssr } from '../index.js';
@@ -61,7 +60,6 @@ export async function load_node({
* @param {RequestInfo} resource
* @param {RequestInit} opts
*/
- // @ts-ignore mismatch between client fetch and node-fetch
fetch: async (resource, opts = {}) => {
/** @type {string} */
let url;
@@ -98,10 +96,7 @@ export async function load_node({
if (parsed.protocol) {
// external fetch
- response = await fetch(
- parsed.href,
- /** @type {import('node-fetch').RequestInit} */ (opts)
- );
+ response = await fetch(parsed.href, /** @type {RequestInit} */ (opts));
} else {
// otherwise we're dealing with an internal fetch
const resolved = resolve(request.path, parsed.pathname);
@@ -127,7 +122,7 @@ export async function load_node({
// TODO we need to know what protocol to use
response = await fetch(
`http://${page.host}/${asset.file}`,
- /** @type {import('node-fetch').RequestInit} */ (opts)
+ /** @type {RequestInit} */ (opts)
);
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e736003a2815..8bc895f2ae42 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -56,11 +56,9 @@ importers:
'@sveltejs/kit': workspace:*
cookie: ^0.4.1
marked: ^2.0.1
- node-fetch: ^2.6.1
svelte: ^3.35.0
dependencies:
cookie: 0.4.1
- node-fetch: 2.6.1
devDependencies:
'@sveltejs/adapter-node': link:../../packages/adapter-node
'@sveltejs/kit': link:../../packages/kit
@@ -2511,6 +2509,7 @@ packages:
/node-fetch/2.6.1:
resolution: {integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==}
engines: {node: 4.x || >=6.0.0}
+ dev: true
/node-fetch/3.0.0-beta.9:
resolution: {integrity: sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==}