diff --git a/cypress/integration/default/wasm.spec.ts b/cypress/integration/default/wasm.spec.ts
new file mode 100644
index 0000000000..e651431e03
--- /dev/null
+++ b/cypress/integration/default/wasm.spec.ts
@@ -0,0 +1,8 @@
+describe('WebAssembly support', () => {
+ it('generates an API route with wasm chunks', () => {
+ cy.request('/api/og?username=netlify').then((response) => {
+ // Failure state is zero-length body
+ expect(response.body).to.have.length.above(10000)
+ })
+ })
+})
diff --git a/demos/default/next.config.js b/demos/default/next.config.js
index 5321fa20b9..f91db4024b 100644
--- a/demos/default/next.config.js
+++ b/demos/default/next.config.js
@@ -75,8 +75,8 @@ module.exports = {
remotePatterns: [
{
hostname: '*.imgur.com',
- }
- ]
+ },
+ ],
},
// https://nextjs.org/docs/basic-features/built-in-css-support#customizing-sass-options
sassOptions: {
@@ -84,5 +84,5 @@ module.exports = {
},
experimental: {
optimizeCss: false,
- }
+ },
}
diff --git a/demos/default/package.json b/demos/default/package.json
index 3c93b24290..d6a18f82fe 100644
--- a/demos/default/package.json
+++ b/demos/default/package.json
@@ -20,6 +20,7 @@
"dependencies": {
"@reach/dialog": "^0.16.2",
"@reach/visually-hidden": "^0.16.0",
+ "@vercel/og": "^0.0.15",
"next": "^12.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
diff --git a/demos/default/pages/api/og.tsx b/demos/default/pages/api/og.tsx
new file mode 100644
index 0000000000..75f1069bac
--- /dev/null
+++ b/demos/default/pages/api/og.tsx
@@ -0,0 +1,48 @@
+import { ImageResponse } from '@vercel/og'
+import { NextRequest } from 'next/server'
+
+export const config = {
+ runtime: 'experimental-edge',
+}
+
+export default async function handler(req: NextRequest) {
+ const { searchParams } = req.nextUrl
+ const username = searchParams.get('username')
+
+ return new ImageResponse(
+ (
+
+

+ {username ?
github.com/{username}
:
Visit with "?username=netlify"
}
+
+ ),
+ {
+ width: 1200,
+ height: 630,
+ headers: {
+ // By default this has an immutable cache, but this is for testing
+ 'Cache-Control': 'public, max-age=0, must-revalidate',
+ },
+ },
+ )
+}
diff --git a/package-lock.json b/package-lock.json
index 7092720830..d6684dc803 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -115,6 +115,7 @@
"dependencies": {
"@reach/dialog": "^0.16.2",
"@reach/visually-hidden": "^0.16.0",
+ "@vercel/og": "^0.0.15",
"next": "^12.3.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
@@ -5175,12 +5176,35 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@resvg/resvg-wasm": {
+ "version": "2.0.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.0.0-alpha.4.tgz",
+ "integrity": "sha512-pWIG9a/x1ky8gXKRhPH1OPKpHFoMN1ISLbJ+O+gPXQHIAKhNd5I28RlWf7q576hAOQA9JZTlo3p/M2uyLzJmmw==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@rushstack/eslint-patch": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
+ "node_modules/@shuding/opentype.js": {
+ "version": "1.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
+ "dependencies": {
+ "fflate": "^0.7.3",
+ "string.prototype.codepointat": "^0.2.1"
+ },
+ "bin": {
+ "ot": "bin/ot"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
"node_modules/@sindresorhus/is": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
@@ -5550,13 +5574,13 @@
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
- "devOptional": true
+ "dev": true
},
"node_modules/@types/react": {
"version": "17.0.50",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.50.tgz",
"integrity": "sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -5582,7 +5606,7 @@
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
- "devOptional": true
+ "dev": true
},
"node_modules/@types/sinonjs__fake-timers": {
"version": "8.1.1",
@@ -5633,6 +5657,11 @@
"@types/node": "*"
}
},
+ "node_modules/@types/yoga-layout": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
+ "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw=="
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz",
@@ -5981,6 +6010,19 @@
"resolved": "https://registry.npmjs.org/@vercel/node-bridge/-/node-bridge-2.2.2.tgz",
"integrity": "sha512-haGBC8noyA5BfjCRXRH+VIkHCDVW5iD5UX24P2nOdilwUxI4qWsattS/co8QBGq64XsNLRAMdM5pQUE3zxkF9Q=="
},
+ "node_modules/@vercel/og": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.0.15.tgz",
+ "integrity": "sha512-AHa25M8Hxt+83aSAY+GPFYuE1oTNGzbw1sJZBa9AsYNUW6vmXPTGW9jJmOUnkjp2dp+wyY3gtAZRa26HjSeNSw==",
+ "dependencies": {
+ "@resvg/resvg-wasm": "2.0.0-alpha.4",
+ "satori": "0.0.38",
+ "yoga-wasm-web": "0.1.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -7310,6 +7352,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg=="
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001402",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001402.tgz",
@@ -9055,6 +9102,24 @@
"node": ">=8"
}
},
+ "node_modules/css-background-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="
+ },
+ "node_modules/css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
+ },
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/css-select": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
@@ -9071,6 +9136,16 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -9116,7 +9191,7 @@
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
- "devOptional": true
+ "dev": true
},
"node_modules/custom-routes": {
"resolved": "demos/custom-routes",
@@ -11983,6 +12058,11 @@
"node": "^12.20 || >= 14.13"
}
},
+ "node_modules/fflate": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
+ },
"node_modules/figures": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/figures/-/figures-4.0.1.tgz",
@@ -13148,7 +13228,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
"integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
- "devOptional": true
+ "dev": true
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -18900,6 +18980,11 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
"node_modules/postcss-values-parser": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz",
@@ -20318,7 +20403,7 @@
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz",
"integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==",
- "devOptional": true,
+ "dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
@@ -20331,6 +20416,22 @@
"node": ">=12.0.0"
}
},
+ "node_modules/satori": {
+ "version": "0.0.38",
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.0.38.tgz",
+ "integrity": "sha512-o8nMTp5IiLKi3oOw80Y3LdDAReGS+UfC1StQBAwlnFNzHmY1A3JGFT+9gam6x8I6uMDFrs82FnG9R/kI9iONdg==",
+ "dependencies": {
+ "@shuding/opentype.js": "1.4.0-beta.0",
+ "css-background-parser": "^0.1.0",
+ "css-box-shadow": "1.0.0-3",
+ "css-to-react-native": "^3.0.0",
+ "postcss-value-parser": "^4.2.0",
+ "yoga-layout-prebuilt": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@@ -21348,6 +21449,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
@@ -23333,6 +23439,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/yoga-layout-prebuilt": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz",
+ "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==",
+ "dependencies": {
+ "@types/yoga-layout": "1.9.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yoga-wasm-web": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.1.2.tgz",
+ "integrity": "sha512-8SkgawHcA0RUbMrnhxbaQkZDBi8rMed8pQHixkFF9w32zGhAwZ9/cOHWlpYfr6RCx42Yp3siV45/jPEkJxsk6w=="
+ },
"node_modules/zip-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
@@ -25874,8 +25996,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz",
"integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"chalk": {
"version": "5.1.2",
@@ -26226,8 +26347,7 @@
"version": "17.0.0",
"resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz",
"integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-import-resolver-typescript": {
"version": "3.3.0",
@@ -26794,12 +26914,26 @@
}
}
},
+ "@resvg/resvg-wasm": {
+ "version": "2.0.0-alpha.4",
+ "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.0.0-alpha.4.tgz",
+ "integrity": "sha512-pWIG9a/x1ky8gXKRhPH1OPKpHFoMN1ISLbJ+O+gPXQHIAKhNd5I28RlWf7q576hAOQA9JZTlo3p/M2uyLzJmmw=="
+ },
"@rushstack/eslint-patch": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz",
"integrity": "sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==",
"dev": true
},
+ "@shuding/opentype.js": {
+ "version": "1.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz",
+ "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==",
+ "requires": {
+ "fflate": "^0.7.3",
+ "string.prototype.codepointat": "^0.2.1"
+ }
+ },
"@sindresorhus/is": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz",
@@ -27135,13 +27269,13 @@
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
- "devOptional": true
+ "dev": true
},
"@types/react": {
"version": "17.0.50",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.50.tgz",
"integrity": "sha512-ZCBHzpDb5skMnc1zFXAXnL3l1FAdi+xZvwxK+PkglMmBrwjpp9nKaWuEvrGnSifCJmBFGxZOOFuwC6KH/s0NuA==",
- "devOptional": true,
+ "dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -27167,7 +27301,7 @@
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
- "devOptional": true
+ "dev": true
},
"@types/sinonjs__fake-timers": {
"version": "8.1.1",
@@ -27218,6 +27352,11 @@
"@types/node": "*"
}
},
+ "@types/yoga-layout": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@types/yoga-layout/-/yoga-layout-1.9.2.tgz",
+ "integrity": "sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw=="
+ },
"@typescript-eslint/eslint-plugin": {
"version": "5.20.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz",
@@ -27425,6 +27564,16 @@
"resolved": "https://registry.npmjs.org/@vercel/node-bridge/-/node-bridge-2.2.2.tgz",
"integrity": "sha512-haGBC8noyA5BfjCRXRH+VIkHCDVW5iD5UX24P2nOdilwUxI4qWsattS/co8QBGq64XsNLRAMdM5pQUE3zxkF9Q=="
},
+ "@vercel/og": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.0.15.tgz",
+ "integrity": "sha512-AHa25M8Hxt+83aSAY+GPFYuE1oTNGzbw1sJZBa9AsYNUW6vmXPTGW9jJmOUnkjp2dp+wyY3gtAZRa26HjSeNSw==",
+ "requires": {
+ "@resvg/resvg-wasm": "2.0.0-alpha.4",
+ "satori": "0.0.38",
+ "yoga-wasm-web": "0.1.2"
+ }
+ },
"abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -27475,8 +27624,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"acorn-walk": {
"version": "7.2.0",
@@ -28446,6 +28594,11 @@
}
}
},
+ "camelize": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz",
+ "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg=="
+ },
"caniuse-lite": {
"version": "1.0.30001402",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001402.tgz",
@@ -29781,6 +29934,21 @@
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
"dev": true
},
+ "css-background-parser": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz",
+ "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA=="
+ },
+ "css-box-shadow": {
+ "version": "1.0.0-3",
+ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
+ "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
+ },
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
+ },
"css-select": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
@@ -29794,6 +29962,16 @@
"nth-check": "^2.0.1"
}
},
+ "css-to-react-native": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz",
+ "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -29832,7 +30010,7 @@
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
"integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
- "devOptional": true
+ "dev": true
},
"custom-routes": {
"version": "file:demos/custom-routes",
@@ -30174,6 +30352,7 @@
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.25",
+ "@vercel/og": "^0.0.15",
"critters": "^0.0.16",
"husky": "^7.0.4",
"if-env": "^1.0.4",
@@ -30962,8 +31141,7 @@
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
"integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-formatter-codeframe": {
"version": "7.32.1",
@@ -31457,8 +31635,7 @@
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.5.0.tgz",
"integrity": "sha512-8k1gRt7D7h03kd+SAAlzXkQwWK22BnK6GKZG+FJA6BAGy22CFvl8kCIXKpVux0cCxMWDQUPqSok0LKaZ0aOcCw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-plugin-unicorn": {
"version": "43.0.2",
@@ -32077,6 +32254,11 @@
"web-streams-polyfill": "^3.0.3"
}
},
+ "fflate": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
+ "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="
+ },
"figures": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/figures/-/figures-4.0.1.tgz",
@@ -32937,7 +33119,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz",
"integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==",
- "devOptional": true
+ "dev": true
},
"import-fresh": {
"version": "3.3.0",
@@ -33990,8 +34172,7 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"jest-regex-util": {
"version": "27.5.1",
@@ -37343,6 +37524,11 @@
"source-map-js": "^1.0.2"
}
},
+ "postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
+ },
"postcss-values-parser": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz",
@@ -38410,13 +38596,26 @@
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz",
"integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==",
- "devOptional": true,
+ "dev": true,
"requires": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
}
},
+ "satori": {
+ "version": "0.0.38",
+ "resolved": "https://registry.npmjs.org/satori/-/satori-0.0.38.tgz",
+ "integrity": "sha512-o8nMTp5IiLKi3oOw80Y3LdDAReGS+UfC1StQBAwlnFNzHmY1A3JGFT+9gam6x8I6uMDFrs82FnG9R/kI9iONdg==",
+ "requires": {
+ "@shuding/opentype.js": "1.4.0-beta.0",
+ "css-background-parser": "^0.1.0",
+ "css-box-shadow": "1.0.0-3",
+ "css-to-react-native": "^3.0.0",
+ "postcss-value-parser": "^4.2.0",
+ "yoga-layout-prebuilt": "^1.10.0"
+ }
+ },
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@@ -38671,14 +38870,12 @@
"styled-jsx": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.2.tgz",
- "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ==",
- "requires": {}
+ "integrity": "sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ=="
},
"use-sync-external-store": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz",
- "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ==",
- "requires": {}
+ "integrity": "sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ=="
}
}
},
@@ -39396,6 +39593,11 @@
"strip-ansi": "^7.0.1"
}
},
+ "string.prototype.codepointat": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
+ "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
+ },
"string.prototype.matchall": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz",
@@ -39490,8 +39692,7 @@
"styled-jsx": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.6.tgz",
- "integrity": "sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA==",
- "requires": {}
+ "integrity": "sha512-xOeROtkK5MGMDimBQ3J6iPId8q0t/BDoG5XN6oKkZClVz9ISF/hihN8OCn2LggMU6N32aXnrXBdn3auSqNS9fA=="
},
"supports-color": {
"version": "9.2.2",
@@ -40219,8 +40420,7 @@
"ws": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.9.0.tgz",
- "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==",
- "requires": {}
+ "integrity": "sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg=="
}
}
},
@@ -40338,8 +40538,7 @@
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
- "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
- "requires": {}
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="
},
"util-deprecate": {
"version": "1.0.2",
@@ -40744,8 +40943,7 @@
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"xdg-basedir": {
"version": "4.0.0",
@@ -40891,6 +41089,19 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
},
+ "yoga-layout-prebuilt": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz",
+ "integrity": "sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==",
+ "requires": {
+ "@types/yoga-layout": "1.9.2"
+ }
+ },
+ "yoga-wasm-web": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.1.2.tgz",
+ "integrity": "sha512-8SkgawHcA0RUbMrnhxbaQkZDBi8rMed8pQHixkFF9w32zGhAwZ9/cOHWlpYfr6RCx42Yp3siV45/jPEkJxsk6w=="
+ },
"zip-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz",
@@ -40903,4 +41114,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/runtime/src/helpers/edge.ts b/packages/runtime/src/helpers/edge.ts
index 5e09746310..76ee557d64 100644
--- a/packages/runtime/src/helpers/edge.ts
+++ b/packages/runtime/src/helpers/edge.ts
@@ -22,6 +22,10 @@ interface EdgeFunctionDefinitionV1 {
regexp: string
}
+interface AssetRef {
+ name: string
+ filePath: string
+}
export interface MiddlewareMatcher {
regexp: string
locale?: false
@@ -35,6 +39,8 @@ interface EdgeFunctionDefinitionV2 {
name: string
page: string
matchers: MiddlewareMatcher[]
+ wasm?: AssetRef[]
+ assets?: AssetRef[]
}
type EdgeFunctionDefinition = EdgeFunctionDefinitionV1 | EdgeFunctionDefinitionV2
@@ -73,13 +79,33 @@ const sanitizeName = (name: string) => `next_${name.replace(/\W/g, '_')}`
* Initialization added to the top of the edge function bundle
*/
const preamble = /* js */ `
-
-globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
-let _ENTRIES = {}
+import {
+ decode as _base64Decode,
+} from "https://deno.land/std@0.159.0/encoding/base64.ts";
// Deno defines "window", but naughty libraries think this means it's a browser
delete globalThis.window
+globalThis.process = { env: {...Deno.env.toObject(), NEXT_RUNTIME: 'edge', 'NEXT_PRIVATE_MINIMAL_MODE': '1' } }
// Next uses "self" as a function-scoped global-like object
const self = {}
+let _ENTRIES = {}
+
+// Next uses blob: urls to refer to local assets, so we need to intercept these
+const _fetch = globalThis.fetch
+const fetch = async (url, init) => {
+ try {
+ if (typeof url === 'object' && url.href?.startsWith('blob:')) {
+ const key = url.href.slice(5)
+ if (key in _ASSETS) {
+ return new Response(_base64Decode(_ASSETS[key]))
+ }
+ }
+ return await _fetch(url, init)
+ } catch (error) {
+ console.error(error)
+ throw error
+ }
+}
+
`
// Slightly different spacing in different versions!
@@ -99,6 +125,22 @@ const getMiddlewareBundle = async ({
}): Promise => {
const { publish } = netlifyConfig.build
const chunks: Array = [preamble]
+
+ if ('wasm' in edgeFunctionDefinition) {
+ for (const { name, filePath } of edgeFunctionDefinition.wasm) {
+ const wasm = await fs.readFile(join(publish, filePath))
+ chunks.push(`const ${name} = _base64Decode(${JSON.stringify(wasm.toString('base64'))}).buffer`)
+ }
+ }
+
+ if ('assets' in edgeFunctionDefinition) {
+ chunks.push(`const _ASSETS = {}`)
+ for (const { name, filePath } of edgeFunctionDefinition.assets) {
+ const wasm = await fs.readFile(join(publish, filePath))
+ chunks.push(`_ASSETS[${JSON.stringify(name)}] = ${JSON.stringify(wasm.toString('base64'))}`)
+ }
+ }
+
for (const file of edgeFunctionDefinition.files) {
const filePath = join(publish, file)
diff --git a/packages/runtime/src/templates/edge-shared/utils.ts b/packages/runtime/src/templates/edge-shared/utils.ts
index b741bab623..4d9b95cde6 100644
--- a/packages/runtime/src/templates/edge-shared/utils.ts
+++ b/packages/runtime/src/templates/edge-shared/utils.ts
@@ -83,7 +83,10 @@ export const buildResponse = async ({
const transformed = response.dataTransforms.reduce((prev, transform) => {
return transform(prev)
}, props)
- return new Response(JSON.stringify(transformed), response)
+ const body = JSON.stringify(transformed)
+ const headers = new Headers(response.headers)
+ headers.set('content-length', String(body.length))
+ return new Response(body, { ...response, headers })
}
// This var will hold the contents of the script tag
let buffer = ''
diff --git a/packages/runtime/src/templates/edge/bundle.js b/packages/runtime/src/templates/edge/bundle.js
index 0765289028..757d42d69f 100644
--- a/packages/runtime/src/templates/edge/bundle.js
+++ b/packages/runtime/src/templates/edge/bundle.js
@@ -2,6 +2,6 @@
* This placeholder is replaced with the compiled Next.js bundle at build time
* @param {Object} props
* @param {import("./runtime.ts").RequestData} props.request
- * @returns {Promise}
+ * @returns {Promise}
*/
export default async ({ request }) => {}
diff --git a/test/__snapshots__/index.js.snap b/test/__snapshots__/index.js.snap
index a38c10e18f..88c6391bfc 100644
--- a/test/__snapshots__/index.js.snap
+++ b/test/__snapshots__/index.js.snap
@@ -12,6 +12,7 @@ exports.resolvePages = () => {
require.resolve('../../../.next/server/pages/api/hello-background.js')
require.resolve('../../../.next/server/pages/api/hello-scheduled.js')
require.resolve('../../../.next/server/pages/api/hello.js')
+ require.resolve('../../../.next/server/pages/api/og.js')
require.resolve('../../../.next/server/pages/api/shows/[...params].js')
require.resolve('../../../.next/server/pages/api/shows/[id].js')
require.resolve('../../../.next/server/pages/deep/import.js')
@@ -49,6 +50,7 @@ exports.resolvePages = () => {
require.resolve('../../../.next/server/pages/api/hello-background.js')
require.resolve('../../../.next/server/pages/api/hello-scheduled.js')
require.resolve('../../../.next/server/pages/api/hello.js')
+ require.resolve('../../../.next/server/pages/api/og.js')
require.resolve('../../../.next/server/pages/api/shows/[...params].js')
require.resolve('../../../.next/server/pages/api/shows/[id].js')
require.resolve('../../../.next/server/pages/deep/import.js')
@@ -86,6 +88,7 @@ exports.resolvePages = () => {
require.resolve('../../../web/.next/server/pages/api/hello-background.js')
require.resolve('../../../web/.next/server/pages/api/hello-scheduled.js')
require.resolve('../../../web/.next/server/pages/api/hello.js')
+ require.resolve('../../../web/.next/server/pages/api/og.js')
require.resolve('../../../web/.next/server/pages/api/shows/[...params].js')
require.resolve('../../../web/.next/server/pages/api/shows/[id].js')
require.resolve('../../../web/.next/server/pages/deep/import.js')
@@ -123,6 +126,7 @@ exports.resolvePages = () => {
require.resolve('../../../web/.next/server/pages/api/hello-background.js')
require.resolve('../../../web/.next/server/pages/api/hello-scheduled.js')
require.resolve('../../../web/.next/server/pages/api/hello.js')
+ require.resolve('../../../web/.next/server/pages/api/og.js')
require.resolve('../../../web/.next/server/pages/api/shows/[...params].js')
require.resolve('../../../web/.next/server/pages/api/shows/[id].js')
require.resolve('../../../web/.next/server/pages/deep/import.js')
@@ -1116,6 +1120,11 @@ Array [
"status": 404,
"to": "/404.html",
},
+ Object {
+ "from": "/api/og",
+ "status": 200,
+ "to": "/.netlify/functions/_api_og-handler",
+ },
Object {
"from": "/api/shows/:id",
"status": 200,
diff --git a/test/index.js b/test/index.js
index 1a94c77fe6..842ae14f5e 100644
--- a/test/index.js
+++ b/test/index.js
@@ -591,16 +591,16 @@ describe('onBuild()', () => {
process.env.DISABLE_IPX = '1'
await moveNextDist()
await nextRuntime.onBuild(defaultArgs)
- const nextImageRedirect = netlifyConfig.redirects.find(redirect => redirect.from.includes('/_next/image'))
-
+ const nextImageRedirect = netlifyConfig.redirects.find((redirect) => redirect.from.includes('/_next/image'))
+
expect(nextImageRedirect).toBeDefined()
- expect(nextImageRedirect.to).toEqual("/404.html")
+ expect(nextImageRedirect.to).toEqual('/404.html')
expect(nextImageRedirect.status).toEqual(404)
expect(nextImageRedirect.force).toEqual(true)
-
+
delete process.env.DISABLE_IPX
})
-
+
test('generates an ipx edge function by default', async () => {
await moveNextDist()
await nextRuntime.onBuild(defaultArgs)
@@ -618,6 +618,14 @@ describe('onBuild()', () => {
test('does not generate an ipx edge function if Netlify Edge is disabled', async () => {
process.env.NEXT_DISABLE_NETLIFY_EDGE = '1'
await moveNextDist()
+
+ // We need to pretend there's no edge API routes, because otherwise it'll fail
+ // when we try to disable edge runtime.
+ const manifest = path.join('.next', 'server', 'middleware-manifest.json')
+ const manifestContent = await readJson(manifest)
+ manifestContent.functions = {}
+ await writeJSON(manifest, manifestContent)
+
await nextRuntime.onBuild(defaultArgs)
expect(existsSync(path.join('.netlify', 'edge-functions', 'ipx', 'index.ts'))).toBeFalsy()
@@ -1586,6 +1594,13 @@ describe('api route file analysis', () => {
config: { schedule: '@hourly', type: 'experimental-scheduled' },
route: '/api/hello-scheduled',
},
+ {
+ compiled: 'pages/api/og.js',
+ config: {
+ runtime: 'experimental-edge',
+ },
+ route: '/api/og',
+ },
]),
)
})