Skip to content

Commit 4d7ad97

Browse files
piehJakeChampionserhalp
authored
fix: use version of htmlrewriter which does not make use of asyncify, which looks to have a potential memory leak under high load (#2721)
* fix: use version of htmlrewriter which does not make use of asyncify, which looks to have a potential memory leak under high load we noticed the memory issue with Netlify's CSP plugin which used the same htmlrewriter library. We've built a new htmlrewriter library which uses the latest version of lol-html and removes the ability to use async-handlers, which is what required asyncify to be included. * chore: vendor updated htmlrewriter * fix: update remaining htmlrewriter import * fix: workaround deno vendor limitation (not pulling static wasm files) * fix: inline htmlrewriter wasm blob to workaround bundling problems * Update tools/build.js Co-authored-by: Philippe Serhal <[email protected]> --------- Co-authored-by: jake champion <[email protected]> Co-authored-by: Philippe Serhal <[email protected]>
1 parent 6b56128 commit 4d7ad97

File tree

6 files changed

+44
-6
lines changed

6 files changed

+44
-6
lines changed

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'

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
)

tools/build.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { createWriteStream } from 'node:fs'
12
import { cp, rm } from 'node:fs/promises'
2-
import { resolve, join } from 'node:path'
3+
import { join, resolve } from 'node:path'
4+
import { Readable } from 'stream'
5+
import { finished } from 'stream/promises'
36

47
import { build, context } from 'esbuild'
58
import { execaCommand } from 'execa'
@@ -94,6 +97,23 @@ async function vendorDeno() {
9497
console.log(`📦 Vendoring Deno modules into '${vendorDest}'...`)
9598

9699
await execaCommand(`deno vendor ${vendorSource} --output=${vendorDest} --force`)
100+
101+
// htmlrewriter contains wasm files and those don't currently work great with vendoring
102+
// see https://github.com/denoland/deno/issues/14123
103+
// to workaround this we copy the wasm files manually
104+
const filesToDownload = ['https://deno.land/x/[email protected]/pkg/htmlrewriter_bg.wasm']
105+
await Promise.all(
106+
filesToDownload.map(async (urlString) => {
107+
const url = new URL(urlString)
108+
109+
const destination = join(vendorDest, url.hostname, url.pathname)
110+
111+
const res = await fetch(url)
112+
if (!res.ok) throw new Error('Failed to fetch .wasm file to vendor', { cause: err })
113+
const fileStream = createWriteStream(destination, { flags: 'wx' })
114+
await finished(Readable.fromWeb(res.body).pipe(fileStream))
115+
}),
116+
)
97117
}
98118

99119
const args = new Set(process.argv.slice(2))

0 commit comments

Comments
 (0)