@netlify/plugin-nextjs 4.30.4-ipx-layer.0 → 4.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/helpers/edge.js
CHANGED
|
@@ -14,7 +14,7 @@ const config_1 = require("./config");
|
|
|
14
14
|
const matchers_1 = require("./matchers");
|
|
15
15
|
const maybeLoadJson = (path) => {
|
|
16
16
|
if ((0, fs_1.existsSync)(path)) {
|
|
17
|
-
return (0, fs_extra_1.
|
|
17
|
+
return (0, fs_extra_1.readJSON)(path);
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
const isAppDirRoute = (route, appPathRoutesManifest) => Boolean(appPathRoutesManifest) && Object.values(appPathRoutesManifest).includes(route);
|
|
@@ -29,53 +29,6 @@ exports.loadPrerenderManifest = loadPrerenderManifest;
|
|
|
29
29
|
* Convert the Next middleware name into a valid Edge Function name
|
|
30
30
|
*/
|
|
31
31
|
const sanitizeName = (name) => `next_${name.replace(/\W/g, '_')}`;
|
|
32
|
-
/**
|
|
33
|
-
* Initialization added to the top of the edge function bundle
|
|
34
|
-
*/
|
|
35
|
-
const preamble = /* js */ `
|
|
36
|
-
import {
|
|
37
|
-
decode as _base64Decode,
|
|
38
|
-
} from "https://deno.land/std@0.159.0/encoding/base64.ts";
|
|
39
|
-
// Deno defines "window", but naughty libraries think this means it's a browser
|
|
40
|
-
delete globalThis.window
|
|
41
|
-
globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
|
|
42
|
-
globalThis.EdgeRuntime = "netlify-edge"
|
|
43
|
-
let _ENTRIES = {}
|
|
44
|
-
|
|
45
|
-
// Next.js uses this extension to the Headers API implemented by Cloudflare workerd
|
|
46
|
-
if(!('getAll' in Headers.prototype)) {
|
|
47
|
-
Headers.prototype.getAll = function getAll(name) {
|
|
48
|
-
name = name.toLowerCase();
|
|
49
|
-
if (name !== "set-cookie") {
|
|
50
|
-
throw new Error("Headers.getAll is only supported for Set-Cookie");
|
|
51
|
-
}
|
|
52
|
-
return [...this.entries()]
|
|
53
|
-
.filter(([key]) => key === name)
|
|
54
|
-
.map(([, value]) => value);
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
// Next uses blob: urls to refer to local assets, so we need to intercept these
|
|
58
|
-
const _fetch = globalThis.fetch
|
|
59
|
-
const fetch = async (url, init) => {
|
|
60
|
-
try {
|
|
61
|
-
if (typeof url === 'object' && url.href?.startsWith('blob:')) {
|
|
62
|
-
const key = url.href.slice(5)
|
|
63
|
-
if (key in _ASSETS) {
|
|
64
|
-
return new Response(_base64Decode(_ASSETS[key]))
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return await _fetch(url, init)
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error(error)
|
|
70
|
-
throw error
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Next edge runtime uses "self" as a function-scoped global-like object, but some of the older polyfills expect it to equal globalThis
|
|
75
|
-
// See https://nextjs.org/docs/basic-features/supported-browsers-features#polyfills
|
|
76
|
-
const self = { ...globalThis, fetch }
|
|
77
|
-
|
|
78
|
-
`;
|
|
79
32
|
// Slightly different spacing in different versions!
|
|
80
33
|
const IMPORT_UNSUPPORTED = [
|
|
81
34
|
`Object.defineProperty(globalThis,"__import_unsupported"`,
|
|
@@ -86,7 +39,8 @@ const IMPORT_UNSUPPORTED = [
|
|
|
86
39
|
*/
|
|
87
40
|
const getMiddlewareBundle = async ({ edgeFunctionDefinition, netlifyConfig, }) => {
|
|
88
41
|
const { publish } = netlifyConfig.build;
|
|
89
|
-
const
|
|
42
|
+
const shims = await fs_1.promises.readFile(getEdgeTemplatePath('shims.js'), 'utf8');
|
|
43
|
+
const chunks = [shims];
|
|
90
44
|
chunks.push(`export const _DEFINITION = ${JSON.stringify(edgeFunctionDefinition)}`);
|
|
91
45
|
if ('wasm' in edgeFunctionDefinition) {
|
|
92
46
|
for (const { name, filePath } of edgeFunctionDefinition.wasm) {
|
|
@@ -234,7 +188,6 @@ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
|
|
|
234
188
|
var _a;
|
|
235
189
|
const manifest = {
|
|
236
190
|
functions: [],
|
|
237
|
-
layers: [],
|
|
238
191
|
version: 1,
|
|
239
192
|
};
|
|
240
193
|
const edgeFunctionRoot = (0, path_1.resolve)('.netlify', 'edge-functions');
|
|
@@ -246,105 +199,108 @@ const writeEdgeFunctions = async ({ netlifyConfig, routesManifest, }) => {
|
|
|
246
199
|
await (0, fs_extra_1.copy)(getEdgeTemplatePath('../edge-shared'), (0, path_1.join)(edgeFunctionRoot, 'edge-shared'));
|
|
247
200
|
await (0, fs_extra_1.writeJSON)((0, path_1.join)(edgeFunctionRoot, 'edge-shared', 'nextConfig.json'), nextConfig);
|
|
248
201
|
await (0, fs_extra_1.copy)((0, path_1.join)(publish, 'prerender-manifest.json'), (0, path_1.join)(edgeFunctionRoot, 'edge-shared', 'prerender-manifest.json'));
|
|
249
|
-
if
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
await
|
|
256
|
-
await (0,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
202
|
+
// early return if edge is disabled
|
|
203
|
+
if ((0, destr_1.default)(process.env.NEXT_DISABLE_NETLIFY_EDGE)) {
|
|
204
|
+
console.log('Environment variable NEXT_DISABLE_NETLIFY_EDGE has been set, skipping Netlify Edge Function creation.');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const rscFunctions = await (0, exports.writeRscDataEdgeFunction)({
|
|
208
|
+
prerenderManifest: await (0, exports.loadPrerenderManifest)(netlifyConfig),
|
|
209
|
+
appPathRoutesManifest: await (0, exports.loadAppPathRoutesManifest)(netlifyConfig),
|
|
210
|
+
});
|
|
211
|
+
manifest.functions.push(...rscFunctions);
|
|
212
|
+
const middlewareManifest = await (0, exports.loadMiddlewareManifest)(netlifyConfig);
|
|
213
|
+
if (!middlewareManifest) {
|
|
214
|
+
console.error("Couldn't find the middleware manifest");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
let usesEdge = false;
|
|
218
|
+
for (const middleware of middlewareManifest.sortedMiddleware) {
|
|
219
|
+
usesEdge = true;
|
|
220
|
+
const edgeFunctionDefinition = middlewareManifest.middleware[middleware];
|
|
221
|
+
const functionName = sanitizeName(edgeFunctionDefinition.name);
|
|
222
|
+
const matchers = generateEdgeFunctionMiddlewareMatchers({
|
|
223
|
+
edgeFunctionDefinition,
|
|
224
|
+
edgeFunctionRoot,
|
|
225
|
+
nextConfig,
|
|
261
226
|
});
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
227
|
+
await writeEdgeFunction({
|
|
228
|
+
edgeFunctionDefinition,
|
|
229
|
+
edgeFunctionRoot,
|
|
230
|
+
netlifyConfig,
|
|
231
|
+
functionName,
|
|
232
|
+
matchers,
|
|
233
|
+
middleware: true,
|
|
265
234
|
});
|
|
235
|
+
manifest.functions.push(...matchers.map((matcher) => middlewareMatcherToEdgeFunctionDefinition(matcher, functionName)));
|
|
266
236
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
for (const middleware of middlewareManifest.sortedMiddleware) {
|
|
237
|
+
// Functions (i.e. not middleware, but edge SSR and API routes)
|
|
238
|
+
if (typeof middlewareManifest.functions === 'object') {
|
|
239
|
+
// When using the app dir, we also need to check if the EF matches a page
|
|
240
|
+
const appPathRoutesManifest = await (0, exports.loadAppPathRoutesManifest)(netlifyConfig);
|
|
241
|
+
// A map of all route pages to their page regex. This is used for pages dir and appDir.
|
|
242
|
+
const pageRegexMap = new Map([...(routesManifest.dynamicRoutes || []), ...(routesManifest.staticRoutes || [])].map((route) => [
|
|
243
|
+
route.page,
|
|
244
|
+
route.regex,
|
|
245
|
+
]));
|
|
246
|
+
// Create a map of pages-dir routes to their data route regex (appDir uses the same route as the HTML)
|
|
247
|
+
const dataRoutesMap = new Map([...(routesManifest.dataRoutes || [])].map((route) => [route.page, route.dataRouteRegex]));
|
|
248
|
+
for (const edgeFunctionDefinition of Object.values(middlewareManifest.functions)) {
|
|
280
249
|
usesEdge = true;
|
|
281
|
-
const edgeFunctionDefinition = middlewareManifest.middleware[middleware];
|
|
282
250
|
const functionName = sanitizeName(edgeFunctionDefinition.name);
|
|
283
|
-
const matchers = generateEdgeFunctionMiddlewareMatchers({
|
|
284
|
-
edgeFunctionDefinition,
|
|
285
|
-
edgeFunctionRoot,
|
|
286
|
-
nextConfig,
|
|
287
|
-
});
|
|
288
251
|
await writeEdgeFunction({
|
|
289
252
|
edgeFunctionDefinition,
|
|
290
253
|
edgeFunctionRoot,
|
|
291
254
|
netlifyConfig,
|
|
292
255
|
functionName,
|
|
293
|
-
matchers,
|
|
294
|
-
middleware: true,
|
|
295
256
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const functionName = sanitizeName(edgeFunctionDefinition.name);
|
|
312
|
-
await writeEdgeFunction({
|
|
313
|
-
edgeFunctionDefinition,
|
|
314
|
-
edgeFunctionRoot,
|
|
315
|
-
netlifyConfig,
|
|
316
|
-
functionName,
|
|
317
|
-
});
|
|
318
|
-
const pattern = (0, exports.getEdgeFunctionPatternForPage)({
|
|
319
|
-
edgeFunctionDefinition,
|
|
320
|
-
pageRegexMap,
|
|
321
|
-
appPathRoutesManifest,
|
|
322
|
-
});
|
|
257
|
+
const pattern = (0, exports.getEdgeFunctionPatternForPage)({
|
|
258
|
+
edgeFunctionDefinition,
|
|
259
|
+
pageRegexMap,
|
|
260
|
+
appPathRoutesManifest,
|
|
261
|
+
});
|
|
262
|
+
manifest.functions.push({
|
|
263
|
+
function: functionName,
|
|
264
|
+
name: edgeFunctionDefinition.name,
|
|
265
|
+
pattern,
|
|
266
|
+
// cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
|
|
267
|
+
cache: usesAppDir ? 'manual' : undefined,
|
|
268
|
+
});
|
|
269
|
+
// pages-dir page routes also have a data route. If there's a match, add an entry mapping that to the function too
|
|
270
|
+
const dataRoute = dataRoutesMap.get(edgeFunctionDefinition.page);
|
|
271
|
+
if (dataRoute) {
|
|
323
272
|
manifest.functions.push({
|
|
324
273
|
function: functionName,
|
|
325
274
|
name: edgeFunctionDefinition.name,
|
|
326
|
-
pattern,
|
|
327
|
-
// cache: "manual" is currently experimental, so we restrict it to sites that use experimental appDir
|
|
275
|
+
pattern: dataRoute,
|
|
328
276
|
cache: usesAppDir ? 'manual' : undefined,
|
|
329
277
|
});
|
|
330
|
-
// pages-dir page routes also have a data route. If there's a match, add an entry mapping that to the function too
|
|
331
|
-
const dataRoute = dataRoutesMap.get(edgeFunctionDefinition.page);
|
|
332
|
-
if (dataRoute) {
|
|
333
|
-
manifest.functions.push({
|
|
334
|
-
function: functionName,
|
|
335
|
-
name: edgeFunctionDefinition.name,
|
|
336
|
-
pattern: dataRoute,
|
|
337
|
-
cache: usesAppDir ? 'manual' : undefined,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
278
|
}
|
|
341
279
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
280
|
+
}
|
|
281
|
+
if ((0, destr_1.default)(process.env.NEXT_FORCE_EDGE_IMAGES) &&
|
|
282
|
+
!(0, destr_1.default)(process.env.NEXT_DISABLE_EDGE_IMAGES) &&
|
|
283
|
+
!(0, destr_1.default)(process.env.DISABLE_IPX)) {
|
|
284
|
+
usesEdge = true;
|
|
285
|
+
console.log('Using Netlify Edge Functions for image format detection. Set env var "NEXT_DISABLE_EDGE_IMAGES=true" to disable.');
|
|
286
|
+
const edgeFunctionDir = (0, path_1.join)(edgeFunctionRoot, 'ipx');
|
|
287
|
+
await (0, fs_extra_1.ensureDir)(edgeFunctionDir);
|
|
288
|
+
await copyEdgeSourceFile({ edgeFunctionDir, file: 'ipx.ts', target: 'index.ts' });
|
|
289
|
+
await (0, fs_extra_1.copyFile)((0, path_1.join)('.netlify', 'functions-internal', '_ipx', 'imageconfig.json'), (0, path_1.join)(edgeFunctionDir, 'imageconfig.json'));
|
|
290
|
+
manifest.functions.push({
|
|
291
|
+
function: 'ipx',
|
|
292
|
+
name: 'next/image handler',
|
|
293
|
+
path: '/_next/image*',
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
console.log('You are not using Netlify Edge Functions for image format detection. Set env var "NEXT_FORCE_EDGE_IMAGES=true" to enable.');
|
|
298
|
+
}
|
|
299
|
+
if (usesEdge) {
|
|
300
|
+
console.log((0, outdent_1.outdent) `
|
|
301
|
+
✨ Deploying middleware and functions to ${(0, chalk_1.greenBright) `Netlify Edge Functions`} ✨
|
|
302
|
+
This feature is in beta. Please share your feedback here: https://ntl.fyi/next-netlify-edge
|
|
303
|
+
`);
|
|
348
304
|
}
|
|
349
305
|
await (0, fs_extra_1.writeJson)((0, path_1.join)(edgeFunctionRoot, 'manifest.json'), manifest);
|
|
350
306
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/plugin-nextjs",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.31.0",
|
|
4
4
|
"description": "Run Next.js seamlessly on Netlify",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@delucis/if-env": "^1.1.2",
|
|
39
|
-
"@netlify/build": "^29.
|
|
39
|
+
"@netlify/build": "^29.6.4",
|
|
40
40
|
"@types/fs-extra": "^9.0.13",
|
|
41
41
|
"@types/jest": "^27.4.1",
|
|
42
42
|
"@types/merge-stream": "^1.1.2",
|
|
43
43
|
"@types/node": "^17.0.25",
|
|
44
|
-
"next": "^13.
|
|
44
|
+
"next": "^13.1.6",
|
|
45
45
|
"npm-run-all": "^4.1.5",
|
|
46
46
|
"typescript": "^4.6.3"
|
|
47
47
|
},
|
|
@@ -1,5 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { Accepts } from 'https://deno.land/x/accepts@2.1.1/mod.ts'
|
|
2
|
+
import type { Context } from 'https://edge.netlify.com'
|
|
3
|
+
// Available at build time
|
|
3
4
|
import imageconfig from './imageconfig.json' assert { type: 'json' }
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
const defaultFormat = 'webp'
|
|
7
|
+
|
|
8
|
+
interface ImageConfig extends Record<string, unknown> {
|
|
9
|
+
formats?: string[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Checks if a URL param is numeric
|
|
13
|
+
const isNumeric = (value: string | null) => Number(value).toString() === value
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Implement content negotiation for images
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// deno-lint-ignore require-await
|
|
20
|
+
const handler = async (req: Request, context: Context) => {
|
|
21
|
+
const { searchParams } = new URL(req.url)
|
|
22
|
+
const accept = new Accepts(req.headers)
|
|
23
|
+
const { formats = [defaultFormat] } = imageconfig as ImageConfig
|
|
24
|
+
if (formats.length === 0) {
|
|
25
|
+
formats.push(defaultFormat)
|
|
26
|
+
}
|
|
27
|
+
let type = accept.types(formats) || defaultFormat
|
|
28
|
+
if (Array.isArray(type)) {
|
|
29
|
+
type = type[0]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const source = searchParams.get('url')
|
|
33
|
+
const width = searchParams.get('w')
|
|
34
|
+
const quality = searchParams.get('q') ?? '75'
|
|
35
|
+
|
|
36
|
+
const errors: Array<string> = []
|
|
37
|
+
|
|
38
|
+
if (!source) {
|
|
39
|
+
errors.push('Missing "url" parameter')
|
|
40
|
+
} else if (!source.startsWith('http') && !source.startsWith('/')) {
|
|
41
|
+
errors.push('The "url" parameter must be a valid URL or path')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!width) {
|
|
45
|
+
errors.push('Missing "w" parameter')
|
|
46
|
+
} else if (!isNumeric(width)) {
|
|
47
|
+
errors.push('Invalid "w" parameter')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!isNumeric(quality)) {
|
|
51
|
+
errors.push('Invalid "q" parameter')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!source || errors.length > 0) {
|
|
55
|
+
return new Response(`Invalid request: \n${errors.join('\n')}`, {
|
|
56
|
+
status: 400,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const modifiers = [`w_${width}`, `q_${quality}`]
|
|
61
|
+
|
|
62
|
+
if (type) {
|
|
63
|
+
if (type.includes('/')) {
|
|
64
|
+
// If this is a mimetype, strip "image/"
|
|
65
|
+
type = type.split('/')[1]
|
|
66
|
+
}
|
|
67
|
+
modifiers.push(`f_${type}`)
|
|
68
|
+
}
|
|
69
|
+
const target = `/_ipx/${modifiers.join(',')}/${encodeURIComponent(source)}`
|
|
70
|
+
return context.rewrite(target)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default handler
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// deno-lint-ignore-file no-var prefer-const no-unused-vars no-explicit-any
|
|
3
|
+
import { decode as _base64Decode } from 'https://deno.land/std@0.175.0/encoding/base64.ts'
|
|
4
|
+
import { AsyncLocalStorage as ALSCompat } from 'https://deno.land/std@0.175.0/node/async_hooks.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* These are the shims, polyfills and other kludges to make Next.js work in standards-compliant runtime.
|
|
8
|
+
* This file isn't imported, but is instead inlined along with other chunks into the edge bundle.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Deno defines "window", but naughty libraries think this means it's a browser
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
delete globalThis.window
|
|
14
|
+
globalThis.process = {
|
|
15
|
+
env: { ...Deno.env.toObject(), NEXT_RUNTIME: 'edge', NEXT_PRIVATE_MINIMAL_MODE: '1' },
|
|
16
|
+
}
|
|
17
|
+
globalThis.EdgeRuntime = 'netlify-edge'
|
|
18
|
+
let _ENTRIES = {}
|
|
19
|
+
|
|
20
|
+
// Next.js expects this as a global
|
|
21
|
+
globalThis.AsyncLocalStorage = ALSCompat
|
|
22
|
+
|
|
23
|
+
// Next.js uses this extension to the Headers API implemented by Cloudflare workerd
|
|
24
|
+
if (!('getAll' in Headers.prototype)) {
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
Headers.prototype.getAll = function getAll(name) {
|
|
27
|
+
name = name.toLowerCase()
|
|
28
|
+
if (name !== 'set-cookie') {
|
|
29
|
+
throw new Error('Headers.getAll is only supported for Set-Cookie')
|
|
30
|
+
}
|
|
31
|
+
return [...this.entries()].filter(([key]) => key === name).map(([, value]) => value)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Next uses blob: urls to refer to local assets, so we need to intercept these
|
|
35
|
+
const _fetch = globalThis.fetch
|
|
36
|
+
const fetch /* type {typeof globalThis.fetch} */ = async (url, init) => {
|
|
37
|
+
try {
|
|
38
|
+
if (url instanceof URL && url.href?.startsWith('blob:')) {
|
|
39
|
+
const key = url.href.slice(5)
|
|
40
|
+
if (key in _ASSETS) {
|
|
41
|
+
return new Response(_base64Decode(_ASSETS[key]))
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return await _fetch(url, init)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(error)
|
|
47
|
+
throw error
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Next edge runtime uses "self" as a function-scoped global-like object, but some of the older polyfills expect it to equal globalThis
|
|
52
|
+
// See https://nextjs.org/docs/basic-features/supported-browsers-features#polyfills
|
|
53
|
+
const self = { ...globalThis, fetch }
|