Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/edge-function-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Edge Function Checks

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'netlify/edge-functions/**'
- '.github/workflows/edge-function-checks.yml'

jobs:
deno-check:
name: Type-check edge functions (no remote imports)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x

# --no-remote locks in the zero-remote-imports property won by vendoring
# deno_dom: this fails fast if a remote (https://) import is ever
# reintroduced into an edge function, in addition to type-checking them.
- name: Type-check with no remote imports
run: deno check --no-remote netlify/edge-functions/*.ts netlify/edge-functions/*.js
91 changes: 91 additions & 0 deletions netlify/edge-functions/_vendor/deno_dom/VENDOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Vendored: deno_dom

Self-contained copy of [`b-fuze/deno-dom`](https://github.com/b-fuze/deno-dom),
vendored so that deploys do not depend on `deno.land` being up (availability)
or unchanged (supply chain). Imported only by
`netlify/edge-functions/proxy-api-docs.js`.

- **Upstream repo:** https://github.com/b-fuze/deno-dom
- **Tag:** `v0.1.56` (commit `92ff96009081a5ce1d983fc725e1d512c0c50eff`)
- **Modifications:** none — every vendored upstream file is byte-identical to
upstream at that tag. The only additions are this file (`VENDOR.md`) and the
repo-owned entry point `deno-dom-wasm-inline.ts` (see below).
- **Subset:** the minimal file set reachable from `deno-dom-wasm.ts` (the
WASM-backed entry point, with the WASM binary inlined) — `src/**` and
`build/deno-wasm/**`. Native-plugin and test files are intentionally
excluded.

## Entry point: use `deno-dom-wasm-inline.ts`, not `deno-dom-wasm.ts`

Upstream's `deno-dom-wasm.ts` loads WASM through
`build/deno-wasm/deno-wasm-dynamic.js`, which on Deno >= 2.1 performs a raw
`await import("./deno-wasm_bg.wasm")` at module load with no fallback. If the
edge bundler doesn't carry local `.wasm` modules, that import fails at module
evaluation and every request to the importing edge function dies before its
handler runs — and the branch taken flips silently whenever Netlify upgrades
the edge runtime's Deno version.

`deno-dom-wasm-inline.ts` (repo-owned, not upstream) always loads the
base64-inlined WASM module (`build/deno-wasm/deno-wasm_bg-wasm.js`), which is
plain JS and bundler-safe on every runtime. Import that entry point. Upstream's
`deno-dom-wasm.ts`, the dynamic loader, and the raw `.wasm` binary are kept
byte-identical for re-vendor fidelity but are intentionally outside the module
graph.

## Re-vendor

```sh
git clone --depth 1 --branch v0.1.56 https://github.com/b-fuze/deno-dom /tmp/deno-dom
cd netlify/edge-functions/_vendor/deno_dom
find . -type f ! -name VENDOR.md ! -name deno-dom-wasm-inline.ts | while read -r f; do
cp "/tmp/deno-dom/$f" "$f"
done
```

To bump the version, change the tag above, re-run, fix any moved/renamed
files, and update this file.

## Verify byte-identity

```sh
git clone --depth 1 --branch v0.1.56 https://github.com/b-fuze/deno-dom /tmp/deno-dom
cd netlify/edge-functions/_vendor/deno_dom
find . -type f ! -name VENDOR.md ! -name deno-dom-wasm-inline.ts | while read -r f; do
cmp "$f" "/tmp/deno-dom/$f" || echo "DIFFERS: $f"
done
```

## File list (28 upstream files + 1 repo-owned)

Repo-owned (excluded from re-vendor/verify): `deno-dom-wasm-inline.ts`

```
deno-dom-wasm.ts
build/deno-wasm/deno-wasm-dynamic.js
build/deno-wasm/deno-wasm.js
build/deno-wasm/deno-wasm_bg-wasm.js
build/deno-wasm/deno-wasm_bg.wasm
build/deno-wasm/env.js
build/deno-wasm/wbg.js
src/api.ts
src/constructor-lock.ts
src/deserialize.ts
src/parser.ts
src/dom/document-fragment.ts
src/dom/document.ts
src/dom/dom-parser.ts
src/dom/element.ts
src/dom/elements/html-template-element.ts
src/dom/html-collection.ts
src/dom/node-list.ts
src/dom/node.ts
src/dom/string-cache.ts
src/dom/utils-types.ts
src/dom/utils.ts
src/dom/selectors/custom-api.ts
src/dom/selectors/nwsapi-types.ts
src/dom/selectors/nwsapi.js
src/dom/selectors/selectors.ts
src/dom/selectors/sizzle-types.ts
src/dom/selectors/sizzle.js
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// FIXME: find a better way to use the raw output from wasm-pack
// instead of replacing the JS entrypoint wholesale like this

import { prepareExports } from "./deno-wasm.js";

function hasSuitableDenoVersion(version) {
const [major, minor] = version.split(".").map(Number);
return major >= 2 && minor >= 1;
}

const wasmImports = await (async () => {
let moduleImports;

if (
typeof Deno === "object" &&
hasSuitableDenoVersion(Deno.version?.deno || "0.0.0")
) {
moduleImports = await import("./deno-wasm_bg.wasm");
} else {
moduleImports = (await import("./deno-wasm_bg-wasm.js")).default;
}

const { parse, parse_frag, ...remappedImports } = moduleImports;
remappedImports.parse_wasm = parse;
remappedImports.parse_frag_wasm = parse_frag;

return remappedImports;
})();

export const { parse, parse_frag } = prepareExports(wasmImports);
160 changes: 160 additions & 0 deletions netlify/edge-functions/_vendor/deno_dom/build/deno-wasm/deno-wasm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// FIXME: find a better way to use the raw output from wasm-pack
// instead of replacing the JS entrypoint wholesale like this

export function prepareExports(wasmImports) {
const {
memory,
__wbindgen_export_0,
__wbindgen_malloc,
__wbindgen_realloc,
__wbindgen_free,
parse_wasm,
parse_frag_wasm,
} = wasmImports;

{
const table = __wbindgen_export_0;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
}

let WASM_VECTOR_LEN = 0;

let cachedUint8ArrayMemory0 = null;

function getUint8ArrayMemory0() {
if (
cachedUint8ArrayMemory0 === null ||
cachedUint8ArrayMemory0.byteLength === 0
) {
cachedUint8ArrayMemory0 = new Uint8Array(memory.buffer);
}
return cachedUint8ArrayMemory0;
}

const cachedTextEncoder = typeof TextEncoder !== "undefined"
? new TextEncoder("utf-8")
: {
encode: () => {
throw Error("TextEncoder not available");
},
};

const encodeString = function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
};

function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}

let len = arg.length;
let ptr = malloc(len, 1) >>> 0;

const mem = getUint8ArrayMemory0();

let offset = 0;

for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}

if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);

offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}

WASM_VECTOR_LEN = offset;
return ptr;
}

const cachedTextDecoder = typeof TextDecoder !== "undefined"
? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true })
: {
decode: () => {
throw Error("TextDecoder not available");
},
};

if (typeof TextDecoder !== "undefined") cachedTextDecoder.decode();

function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(
getUint8ArrayMemory0().subarray(ptr, ptr + len),
);
}

/**
* @param {string} html
* @returns {string}
*/
function parse(html) {
let deferred2_0;
let deferred2_1;
try {
const ptr0 = passStringToWasm0(
html,
__wbindgen_malloc,
__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
const ret = parse_wasm(ptr0, len0);
deferred2_0 = ret[0];
deferred2_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}

/**
* @param {string} html
* @param {string} context_local_name
* @returns {string}
*/
function parse_frag(html, context_local_name) {
let deferred3_0;
let deferred3_1;
try {
const ptr0 = passStringToWasm0(
html,
__wbindgen_malloc,
__wbindgen_realloc,
);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(
context_local_name,
__wbindgen_malloc,
__wbindgen_realloc,
);
const len1 = WASM_VECTOR_LEN;
const ret = parse_frag_wasm(ptr0, len0, ptr1, len1);
deferred3_0 = ret[0];
deferred3_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
__wbindgen_free(deferred3_0, deferred3_1, 1);
}
}

return { parse, parse_frag };
}

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const now = performance.now;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function __wbindgen_init_externref_table() {}
39 changes: 39 additions & 0 deletions netlify/edge-functions/_vendor/deno_dom/deno-dom-wasm-inline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Repo-owned entry point — NOT an upstream deno_dom file (see VENDOR.md).
*
* Upstream's `deno-dom-wasm.ts` → `build/deno-wasm/deno-wasm-dynamic.js`
* picks a WASM loader at module load based on the Deno version: on Deno >= 2.1
* it does `await import("./deno-wasm_bg.wasm")` — a raw local .wasm module
* import with NO fallback. If the platform bundler (Netlify's eszip) doesn't
* carry a local .wasm file as a module, that top-level import fails at module
* evaluation and every request to the importing edge function dies before its
* handler runs. The branch taken also silently changes whenever Netlify
* upgrades the edge runtime's Deno version.
*
* This entry point removes both hazards: it always uses upstream's inlined
* base64 WASM module (`deno-wasm_bg-wasm.js` — plain JS, bundler-safe
* everywhere) and never touches the raw .wasm file or the version branch.
* It replicates the export remap that `deno-wasm-dynamic.js` performs, then
* registers the parser exactly like upstream's `deno-dom-wasm.ts`.
*/
import { prepareExports } from "./build/deno-wasm/deno-wasm.js";
import wasmExports from "./build/deno-wasm/deno-wasm_bg-wasm.js";
import { register } from "./src/parser.ts";

const { parse: parse_wasm, parse_frag: parse_frag_wasm, ...remappedImports } =
wasmExports;
const { parse, parse_frag } = prepareExports({
...remappedImports,
parse_wasm,
parse_frag_wasm,
});

register(
parse,
parse_frag as unknown as (
html: string,
context_local_name?: string,
) => string,
);

export * from "./src/api.ts";
34 changes: 34 additions & 0 deletions netlify/edge-functions/_vendor/deno_dom/deno-dom-wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* @module
*
* This module exposes the Deno DOM API with the WASM (Web Assembly) backend
*
* @example
* ```typescript
* import { DOMParser, Element } from "jsr:@b-fuze/deno-dom";
*
* const doc = new DOMParser().parseFromString(
* `
* <h1>Hello World!</h1>
* <p>Hello from <a href="https://deno.land/">Deno!</a></p>
* `,
* "text/html",
* );
*
* const p = doc.querySelector("p")!;
* console.log(p.textContent); // "Hello from Deno!"
* ```
*/

import { parse, parse_frag } from "./build/deno-wasm/deno-wasm-dynamic.js";
import { register } from "./src/parser.ts";

register(
parse,
parse_frag as unknown as (
html: string,
context_local_name?: string,
) => string,
);

export * from "./src/api.ts";
Loading
Loading