Skip to content

Commit d36d9bf

Browse files
authored
Merge branch 'main' into minivan/free-github-squares
2 parents 97dcdc7 + 6a2c39c commit d36d9bf

31 files changed

+278
-55
lines changed

.github/workflows/pre-release.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
- name: Install Deno
2424
uses: denoland/setup-deno@v1
2525
with:
26-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17
27-
deno-version: v1.37.0
26+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
27+
deno-version: v1.44.4
2828
- name: Extract tag and version
2929
id: extract
3030
run: |-

.github/workflows/release-please.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ jobs:
3131
- name: Install Deno
3232
uses: denoland/setup-deno@v1
3333
with:
34-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17
35-
deno-version: v1.37.0
34+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
35+
deno-version: v1.44.4
3636
- name: Build
3737
run: npm run build
3838
if: ${{ steps.release.outputs.release_created }}

.github/workflows/run-tests.yml

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ jobs:
6565
- name: Install Deno
6666
uses: denoland/setup-deno@v1
6767
with:
68-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
69-
deno-version: v1.37.0
68+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
69+
deno-version: v1.44.4
7070
- name: 'Install dependencies'
7171
run: npm ci
7272
- name: 'Prepare Netlify CLI'
@@ -134,7 +134,7 @@ jobs:
134134
uses: denoland/setup-deno@v1
135135
with:
136136
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17
137-
deno-version: v1.37.0
137+
deno-version: v1.44.4
138138
- name: 'Install dependencies'
139139
run: npm ci
140140
- name: 'Build'
@@ -198,8 +198,8 @@ jobs:
198198
- name: Install Deno
199199
uses: denoland/setup-deno@v1
200200
with:
201-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
202-
deno-version: v1.37.0
201+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
202+
deno-version: v1.44.4
203203
- name: 'Install dependencies'
204204
run: npm ci
205205
- name: 'Build'

.github/workflows/size-check.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
- name: Install Deno
2424
uses: denoland/setup-deno@v1
2525
with:
26-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
27-
deno-version: v1.37.0
26+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
27+
deno-version: v1.44.4
2828
- run: npm ci
2929

3030
- name: Package size report

.github/workflows/test-e2e.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ jobs:
161161
- name: Install Deno
162162
uses: denoland/setup-deno@v1
163163
with:
164-
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/main/node/bridge.ts#L17
165-
deno-version: v1.37.0
164+
# Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20
165+
deno-version: v1.44.4
166166

167167
- name: install runtime
168168
run: npm install --ignore-scripts

.release-please-manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "5.8.1"
2+
".": "5.9.1"
33
}

CHANGELOG.md

+19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## [5.9.1](https://github.com/opennextjs/opennextjs-netlify/compare/v5.9.0...v5.9.1) (2024-12-18)
4+
5+
6+
### Bug Fixes
7+
8+
* use version of htmlrewriter which does not make use of asyncify, which looks to have a potential memory leak under high load ([#2721](https://github.com/opennextjs/opennextjs-netlify/issues/2721)) ([4d7ad97](https://github.com/opennextjs/opennextjs-netlify/commit/4d7ad97c3f16b01000989bde06352f86b5d526ba))
9+
10+
## [5.9.0](https://github.com/opennextjs/opennextjs-netlify/compare/v5.8.1...v5.9.0) (2024-12-09)
11+
12+
13+
### Features
14+
15+
* support after() ([#2717](https://github.com/opennextjs/opennextjs-netlify/issues/2717)) ([51e5373](https://github.com/opennextjs/opennextjs-netlify/commit/51e5373e4914e9b76edf439e8de01c561742bdaa))
16+
17+
18+
### Bug Fixes
19+
20+
* add data request query param to the cache key ([#2701](https://github.com/opennextjs/opennextjs-netlify/issues/2701)) ([00e3a4b](https://github.com/opennextjs/opennextjs-netlify/commit/00e3a4be19bf8428ea6ce1ed2ae74a8ac8375532))
21+
322
## [5.8.1](https://github.com/netlify/next-runtime/compare/v5.8.0...v5.8.1) (2024-10-21)
423

524

deno.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
},
77
"imports": {
88
"@netlify/edge-functions": "https://edge.netlify.com/v1/index.ts"
9-
}
9+
},
10+
"importMap": "./edge-runtime/vendor/import_map.json"
1011
}

edge-runtime/lib/middleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Context } from '@netlify/edge-functions'
22

3-
import { ElementHandlers } from '../vendor/deno.land/x/[email protected]/index.ts'
3+
import type { ElementHandlers } from '../vendor/deno.land/x/[email protected]/src/index.ts'
44

55
type NextDataTransform = <T>(data: T) => T
66

edge-runtime/lib/response.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Context } from '@netlify/edge-functions'
2-
import { HTMLRewriter } from '../vendor/deno.land/x/[email protected]/index.ts'
2+
import {
3+
HTMLRewriter,
4+
type TextChunk,
5+
} from '../vendor/deno.land/x/[email protected]/src/index.ts'
36

47
import { updateModifiedHeaders } from './headers.ts'
58
import type { StructuredLogger } from './logging.ts'
@@ -79,7 +82,7 @@ export const buildResponse = async ({
7982

8083
if (response.dataTransforms.length > 0) {
8184
rewriter.on('script[id="__NEXT_DATA__"]', {
82-
text(textChunk) {
85+
text(textChunk: TextChunk) {
8386
// Grab all the chunks in the Next data script tag
8487
buffer += textChunk.text
8588
if (textChunk.lastInTextNode) {

edge-runtime/vendor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ import 'https://deno.land/[email protected]/node/util.ts'
1212
import 'https://deno.land/[email protected]/path/mod.ts'
1313

1414
import 'https://deno.land/x/[email protected]/index.ts'
15-
import 'https://deno.land/x/[email protected]/index.ts'
15+
import 'https://deno.land/x/[email protected]/src/index.ts'
1616

1717
import 'https://v1-7-0--edge-utils.netlify.app/logger/mod.ts'

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@netlify/plugin-nextjs",
3-
"version": "5.8.1",
3+
"version": "5.9.1",
44
"description": "Run Next.js seamlessly on Netlify",
55
"main": "./dist/index.js",
66
"type": "module",

src/build/functions/edge.ts

+14
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,27 @@ const writeHandlerFile = async (ctx: PluginContext, { matchers, name }: NextDefi
8585
JSON.stringify(minimalNextConfig),
8686
)
8787

88+
const htmlRewriterWasm = await readFile(
89+
join(
90+
ctx.pluginDir,
91+
'edge-runtime/vendor/deno.land/x/[email protected]/pkg/htmlrewriter_bg.wasm',
92+
),
93+
)
94+
8895
// Writing the function entry file. It wraps the middleware code with the
8996
// compatibility layer mentioned above.
9097
await writeFile(
9198
join(handlerDirectory, `${handlerName}.js`),
9299
`
100+
import { decode as _base64Decode } from './edge-runtime/vendor/deno.land/[email protected]/encoding/base64.ts';
101+
import { init as htmlRewriterInit } from './edge-runtime/vendor/deno.land/x/[email protected]/src/index.ts'
93102
import {handleMiddleware} from './edge-runtime/middleware.ts';
94103
import handler from './server/${name}.js';
104+
105+
await htmlRewriterInit({ module_or_path: _base64Decode(${JSON.stringify(
106+
htmlRewriterWasm.toString('base64'),
107+
)}).buffer });
108+
95109
export default (req, context) => handleMiddleware(req, context, handler);
96110
`,
97111
)

src/build/templates/handler-monorepo.tmpl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default async function (req, context) {
1616
tracing.start()
1717
}
1818

19-
const requestContext = createRequestContext(req)
19+
const requestContext = createRequestContext(req, context)
2020
const tracer = getTracer()
2121

2222
const handlerResponse = await runWithRequestContext(requestContext, () => {

src/build/templates/handler.tmpl.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default async function handler(req, context) {
1313
if (process.env.NETLIFY_OTLP_TRACE_EXPORTER_URL) {
1414
tracing.start()
1515
}
16-
const requestContext = createRequestContext(req)
16+
const requestContext = createRequestContext(req, context)
1717
const tracer = getTracer()
1818

1919
const handlerResponse = await runWithRequestContext(requestContext, () => {

src/run/handlers/request-context.cts

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { AsyncLocalStorage } from 'node:async_hooks'
22

3+
import type { Context } from '@netlify/functions'
34
import { LogLevel, systemLogger } from '@netlify/functions/internal'
45

56
import type { NetlifyCachedRouteValue } from '../../shared/cache-types.cjs'
67

78
type SystemLogger = typeof systemLogger
89

10+
// TODO: remove once public types are updated
11+
export interface FutureContext extends Context {
12+
waitUntil?: (promise: Promise<unknown>) => void
13+
}
14+
915
export type RequestContext = {
1016
captureServerTiming: boolean
1117
responseCacheGetLastModified?: number
@@ -16,25 +22,33 @@ export type RequestContext = {
1622
serverTiming?: string
1723
routeHandlerRevalidate?: NetlifyCachedRouteValue['revalidate']
1824
/**
19-
* Track promise running in the background and need to be waited for
25+
* Track promise running in the background and need to be waited for.
26+
* Uses `context.waitUntil` if available, otherwise stores promises to
27+
* await on.
2028
*/
2129
trackBackgroundWork: (promise: Promise<unknown>) => void
2230
/**
23-
* Promise that need to be executed even if response was already sent
31+
* Promise that need to be executed even if response was already sent.
32+
* If `context.waitUntil` is available this promise will be always resolved
33+
* because background work tracking was offloaded to `context.waitUntil`.
2434
*/
2535
backgroundWorkPromise: Promise<unknown>
2636
logger: SystemLogger
2737
}
2838

2939
type RequestContextAsyncLocalStorage = AsyncLocalStorage<RequestContext>
3040

31-
export function createRequestContext(request?: Request): RequestContext {
41+
export function createRequestContext(request?: Request, context?: FutureContext): RequestContext {
3242
const backgroundWorkPromises: Promise<unknown>[] = []
3343

3444
return {
3545
captureServerTiming: request?.headers.has('x-next-debug-logging') ?? false,
3646
trackBackgroundWork: (promise) => {
37-
backgroundWorkPromises.push(promise)
47+
if (context?.waitUntil) {
48+
context.waitUntil(promise)
49+
} else {
50+
backgroundWorkPromises.push(promise)
51+
}
3852
},
3953
get backgroundWorkPromise() {
4054
return Promise.allSettled(backgroundWorkPromises)

src/run/handlers/server.ts

+16-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { OutgoingHttpHeaders } from 'http'
22

33
import { ComputeJsOutgoingMessage, toComputeResponse, toReqRes } from '@fastly/http-compute-js'
4-
import { Context } from '@netlify/functions'
54
import type { NextConfigComplete } from 'next/dist/server/config-shared.js'
65
import type { WorkerRequestHandler } from 'next/dist/server/lib/types.js'
76

@@ -16,9 +15,12 @@ import { nextResponseProxy } from '../revalidate.js'
1615

1716
import { createRequestContext, getLogger, getRequestContext } from './request-context.cjs'
1817
import { getTracer } from './tracer.cjs'
18+
import { setupWaitUntil } from './wait-until.cjs'
1919

2020
const nextImportPromise = import('../next.cjs')
2121

22+
setupWaitUntil()
23+
2224
let nextHandler: WorkerRequestHandler, nextConfig: NextConfigComplete
2325

2426
/**
@@ -44,13 +46,7 @@ const disableFaultyTransferEncodingHandling = (res: ComputeJsOutgoingMessage) =>
4446
}
4547
}
4648

47-
// TODO: remove once https://github.com/netlify/serverless-functions-api/pull/219
48-
// is released and public types are updated
49-
interface FutureContext extends Context {
50-
waitUntil?: (promise: Promise<unknown>) => void
51-
}
52-
53-
export default async (request: Request, context: FutureContext) => {
49+
export default async (request: Request) => {
5450
const tracer = getTracer()
5551

5652
if (!nextHandler) {
@@ -60,10 +56,10 @@ export default async (request: Request, context: FutureContext) => {
6056
nextConfig = await getRunConfig()
6157
setRunConfig(nextConfig)
6258

63-
const { getMockedRequestHandlers } = await nextImportPromise
59+
const { getMockedRequestHandler } = await nextImportPromise
6460
const url = new URL(request.url)
6561

66-
;[nextHandler] = await getMockedRequestHandlers({
62+
nextHandler = await getMockedRequestHandler({
6763
port: Number(url.port) || 443,
6864
hostname: url.hostname,
6965
dir: process.cwd(),
@@ -128,19 +124,20 @@ export default async (request: Request, context: FutureContext) => {
128124
return new Response(body || null, response)
129125
}
130126

131-
if (context.waitUntil) {
132-
context.waitUntil(requestContext.backgroundWorkPromise)
133-
}
134-
135127
const keepOpenUntilNextFullyRendered = new TransformStream({
136128
async flush() {
137129
// it's important to keep the stream open until the next handler has finished
138130
await nextHandlerPromise
139-
if (!context.waitUntil) {
140-
// if waitUntil is not available, we have to keep response stream open until background promises are resolved
141-
// to ensure that all background work executes
142-
await requestContext.backgroundWorkPromise
143-
}
131+
132+
// Next.js relies on `close` event emitted by response to trigger running callback variant of `next/after`
133+
// however @fastly/http-compute-js never actually emits that event - so we have to emit it ourselves,
134+
// otherwise Next would never run the callback variant of `next/after`
135+
res.emit('close')
136+
137+
// We have to keep response stream open until tracked background promises that are don't use `context.waitUntil`
138+
// are resolved. If `context.waitUntil` is available, `requestContext.backgroundWorkPromise` will be empty
139+
// resolved promised and so awaiting it is no-op
140+
await requestContext.backgroundWorkPromise
144141
},
145142
})
146143

src/run/handlers/wait-until.cts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { getRequestContext } from './request-context.cjs'
2+
3+
/**
4+
* @see https://github.com/vercel/next.js/blob/canary/packages/next/src/server/after/builtin-request-context.ts
5+
*/
6+
const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for('@next/request-context')
7+
8+
export type NextJsRequestContext = {
9+
get(): { waitUntil?: (promise: Promise<unknown>) => void } | undefined
10+
}
11+
12+
type GlobalThisWithRequestContext = typeof globalThis & {
13+
[NEXT_REQUEST_CONTEXT_SYMBOL]?: NextJsRequestContext
14+
}
15+
16+
/**
17+
* Registers a `waitUntil` to be used by Next.js for next/after
18+
*/
19+
export function setupWaitUntil() {
20+
// eslint-disable-next-line @typescript-eslint/no-extra-semi
21+
;(globalThis as GlobalThisWithRequestContext)[NEXT_REQUEST_CONTEXT_SYMBOL] = {
22+
get() {
23+
return { waitUntil: getRequestContext()?.trackBackgroundWork }
24+
},
25+
}
26+
}

src/run/next.cts

+5-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export type HtmlBlob = {
8181
isFallback: boolean
8282
}
8383

84-
export async function getMockedRequestHandlers(...args: Parameters<typeof getRequestHandlers>) {
84+
export async function getMockedRequestHandler(...args: Parameters<typeof getRequestHandlers>) {
8585
const tracer = getTracer()
8686
return tracer.withActiveSpan('mocked request handler', async () => {
8787
const ofs = { ...fs }
@@ -127,6 +127,9 @@ export async function getMockedRequestHandlers(...args: Parameters<typeof getReq
127127
require('fs').promises,
128128
)
129129

130-
return getRequestHandlers(...args)
130+
const requestHandlers = await getRequestHandlers(...args)
131+
// depending on Next.js version requestHandlers might be an array of object
132+
// see https://github.com/vercel/next.js/commit/08e7410f15706379994b54c3195d674909a8d533#diff-37243d614f1f5d3f7ea50bbf2af263f6b1a9a4f70e84427977781e07b02f57f1R742
133+
return Array.isArray(requestHandlers) ? requestHandlers[0] : requestHandlers.requestHandler
131134
})
132135
}

0 commit comments

Comments
 (0)