From ad0655712510b57f2072fcb746a34d0fba079af5 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 9 Nov 2022 16:46:41 +0100 Subject: [PATCH 1/5] add test for image generation and record usage --- package.json | 1 + .../webpack/plugins/middleware-plugin.ts | 18 +++++ pnpm-lock.yaml | 80 ++++++++++++++++++- .../image-generation/app/pages/api/image.jsx | 9 +++ .../image-generation/test/index.test.ts | 42 ++++++++++ 5 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 test/integration/image-generation/app/pages/api/image.jsx create mode 100644 test/integration/image-generation/test/index.test.ts diff --git a/package.json b/package.json index a991081002518..f18b46a5c07fb 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@typescript-eslint/eslint-plugin": "4.29.1", "@typescript-eslint/parser": "4.29.1", "@vercel/fetch": "6.1.1", + "@vercel/og": "0.0.20", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index 73c061d51bded..fa3fc8ec5f290 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -686,6 +686,24 @@ function getExtractMetadata(params: { for (const module of modules) { const buildInfo = getModuleBuildInfo(module) + /** + * Check if it uses the image generation feature. + */ + if (!dev) { + const resource = module.resource + if ( + resource && + /[\\/]node_modules[\\/]@vercel[\\/]og[\\/]dist[\\/]index.js$/.test( + resource + ) + ) { + telemetry.record({ + eventName: 'NEXT_EDGE_IMAGE_GENERATION_USED', + payload: {}, + }) + } + } + /** * When building for production checks if the module is using `eval` * and in such case produces a compilation error. The module has to diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed0840840acd1..f08578d3afa2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,7 @@ importers: '@typescript-eslint/eslint-plugin': 4.29.1 '@typescript-eslint/parser': 4.29.1 '@vercel/fetch': 6.1.1 + '@vercel/og': 0.0.20 '@webassemblyjs/ast': 1.11.1 '@webassemblyjs/floating-point-hex-parser': 1.11.1 '@webassemblyjs/helper-api-error': 1.11.1 @@ -217,6 +218,7 @@ importers: '@typescript-eslint/eslint-plugin': 4.29.1_qxyn66xcaddhgaahwkbomftvi4 '@typescript-eslint/parser': 4.29.1_6x3mpmmsttbpxxsctsorxedanu '@vercel/fetch': 6.1.1_fii5qhbaymjqmfm7e2spxc5z4m + '@vercel/og': 0.0.20 '@webassemblyjs/ast': 1.11.1 '@webassemblyjs/floating-point-hex-parser': 1.11.1 '@webassemblyjs/helper-api-error': 1.11.1 @@ -621,8 +623,8 @@ importers: stacktrace-parser: 0.1.10 stream-browserify: 3.0.0 stream-http: 3.1.1 - string_decoder: 1.3.0 string-hash: 1.1.3 + string_decoder: 1.3.0 strip-ansi: 6.0.0 styled-jsx: 5.1.0 tar: 6.1.11 @@ -818,8 +820,8 @@ importers: stacktrace-parser: 0.1.10 stream-browserify: 3.0.0 stream-http: 3.1.1 - string_decoder: 1.3.0 string-hash: 1.1.3 + string_decoder: 1.3.0 strip-ansi: 6.0.0 tar: 6.1.11 taskr: 1.1.0 @@ -6261,6 +6263,11 @@ packages: - utf-8-validate dev: true + /@resvg/resvg-wasm/2.0.0-alpha.4: + resolution: {integrity: sha512-pWIG9a/x1ky8gXKRhPH1OPKpHFoMN1ISLbJ+O+gPXQHIAKhNd5I28RlWf7q576hAOQA9JZTlo3p/M2uyLzJmmw==} + engines: {node: '>= 10'} + dev: true + /@rollup/plugin-alias/3.1.1_rollup@2.35.1: resolution: {integrity: sha512-hNcQY4bpBUIvxekd26DBPgF7BT4mKVNDF5tBG4Zi+3IgwLxGYRY0itHs9D0oLVwXM5pvJDWJlBQro+au8WaUWw==} engines: {node: '>=8.0.0'} @@ -6368,6 +6375,15 @@ packages: ajv: 8.11.0 dev: true + /@shuding/opentype.js/1.4.0-beta.0: + resolution: {integrity: sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==} + engines: {node: '>= 8.0.0'} + hasBin: true + dependencies: + fflate: 0.7.4 + string.prototype.codepointat: 0.2.1 + dev: true + /@sidvind/better-ajv-errors/0.6.10_ajv@6.12.6: resolution: {integrity: sha512-vPv8ks6J1KQW1LPYgxmANxcHniE6LFuekxNpcoUUkotJ2srxP4qXZ+y9qpo5LAXhnLoNP0AH8cninimK68gS6A==} engines: {node: '>= 8.0'} @@ -7373,6 +7389,10 @@ packages: dev: true optional: true + /@types/yoga-layout/1.9.2: + resolution: {integrity: sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==} + dev: true + /@typescript-eslint/eslint-plugin/4.29.1_qxyn66xcaddhgaahwkbomftvi4: resolution: {integrity: sha512-AHqIU+SqZZgBEiWOrtN94ldR3ZUABV5dUG94j8Nms9rQnHFc8fvDOue/58K4CFz6r8OtDDc35Pw9NQPWo0Ayrw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -7604,6 +7624,15 @@ packages: - supports-color dev: true + /@vercel/og/0.0.20: + resolution: {integrity: sha512-089P+TfqWz0xBxjOvOhkZIDDtfrLcye94H4IZ+SqxoGPWpNGXaBvRJER/z5SoJxJRcCAL8tPiK5zdjRskM6tLw==} + engines: {node: '>=16'} + dependencies: + '@resvg/resvg-wasm': 2.0.0-alpha.4 + satori: 0.0.43 + yoga-wasm-web: 0.1.2 + dev: true + /@webassemblyjs/ast/1.11.1: resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} dependencies: @@ -9960,8 +9989,8 @@ packages: engines: {node: '>=10'} hasBin: true dependencies: - is-text-path: 1.0.1 JSONStream: 1.3.5 + is-text-path: 1.0.1 lodash: 4.17.21 meow: 8.1.2 split2: 2.2.0 @@ -10262,6 +10291,10 @@ packages: resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} dev: true + /css-background-parser/0.1.0: + resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} + dev: true + /css-blank-pseudo/3.0.3_postcss@8.4.14: resolution: {integrity: sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==} engines: {node: ^12 || ^14 || >=16} @@ -10273,6 +10306,10 @@ packages: postcss-selector-parser: 6.0.10 dev: true + /css-box-shadow/1.0.0-3: + resolution: {integrity: sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==} + dev: true + /css-color-keywords/1.0.0: resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} engines: {node: '>=4'} @@ -11246,6 +11283,10 @@ packages: engines: {node: '>=10'} dev: true + /emoji-regex/10.2.1: + resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} + dev: true + /emoji-regex/7.0.3: resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==} dev: true @@ -12331,6 +12372,10 @@ packages: pend: 1.2.0 dev: true + /fflate/0.7.4: + resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} + dev: true + /figgy-pudding/3.5.1: resolution: {integrity: sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==} dev: true @@ -21129,6 +21174,19 @@ packages: source-map-js: 1.0.2 dev: true + /satori/0.0.43: + resolution: {integrity: sha512-SzYwr+LsELWRJU9KMviEOE9TdShry+R5AdS54YQvgAVKFDN4yniAIzwQk1/z2TtIx0ceUT9zTeosWAoWvJBEtQ==} + engines: {node: '>=16'} + 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 + emoji-regex: 10.2.1 + postcss-value-parser: 4.2.0 + yoga-layout-prebuilt: 1.10.0 + dev: true + /sax/1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true @@ -21838,6 +21896,10 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /string.prototype.codepointat/0.2.1: + resolution: {integrity: sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==} + dev: true + /string.prototype.matchall/4.0.6: resolution: {integrity: sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==} dependencies: @@ -23044,6 +23106,7 @@ packages: /unified/9.2.0: resolution: {integrity: sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==} dependencies: + '@types/unist': 2.0.3 bail: 1.0.4 extend: 3.0.2 is-buffer: 2.0.4 @@ -24054,6 +24117,17 @@ packages: engines: {node: '>=10'} dev: true + /yoga-layout-prebuilt/1.10.0: + resolution: {integrity: sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==} + engines: {node: '>=8'} + dependencies: + '@types/yoga-layout': 1.9.2 + dev: true + + /yoga-wasm-web/0.1.2: + resolution: {integrity: sha512-8SkgawHcA0RUbMrnhxbaQkZDBi8rMed8pQHixkFF9w32zGhAwZ9/cOHWlpYfr6RCx42Yp3siV45/jPEkJxsk6w==} + dev: true + /zwitch/1.0.5: resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==} dev: true diff --git a/test/integration/image-generation/app/pages/api/image.jsx b/test/integration/image-generation/app/pages/api/image.jsx new file mode 100644 index 0000000000000..518c3d654e00d --- /dev/null +++ b/test/integration/image-generation/app/pages/api/image.jsx @@ -0,0 +1,9 @@ +import { ImageResponse } from '@vercel/og' + +export default async () => { + return new ImageResponse(
hello
) +} + +export const config = { + runtime: 'experimental-edge', +} diff --git a/test/integration/image-generation/test/index.test.ts b/test/integration/image-generation/test/index.test.ts new file mode 100644 index 0000000000000..519274524cdae --- /dev/null +++ b/test/integration/image-generation/test/index.test.ts @@ -0,0 +1,42 @@ +/* eslint-env jest */ +import { + fetchViaHTTP, + findPort, + killApp, + nextBuild, + nextStart, +} from 'next-test-utils' +import { join } from 'path' + +const appDir = join(__dirname, '../app') + +describe('Image Generation', () => { + describe('Prod', () => { + let app + let appPort + + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await killApp(app) + }) + + it('should generate the image without errors', async () => { + const res = await fetchViaHTTP(appPort, '/api/image') + expect(res.status).toBe(200) + expect(res.headers.get('Content-Type')).toBe('image/png') + + const buffer = await res.buffer() + + // It should be a PNG + expect( + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every( + (b, i) => buffer[i] === b + ) + ).toBeTrue() + }) + }) +}) From 1ba7cfeec0bab12ddb7457673d71687fecce39d3 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Wed, 9 Nov 2022 19:45:08 +0100 Subject: [PATCH 2/5] add build feature --- .../build/webpack/plugins/middleware-plugin.ts | 16 +++++++++------- packages/next/telemetry/events/build.ts | 1 + 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/next/build/webpack/plugins/middleware-plugin.ts b/packages/next/build/webpack/plugins/middleware-plugin.ts index fa3fc8ec5f290..08eac95755184 100644 --- a/packages/next/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/build/webpack/plugins/middleware-plugin.ts @@ -27,6 +27,7 @@ import { } from '../../analysis/get-page-static-info' import { Telemetry } from '../../../telemetry/storage' import { traceGlobals } from '../../../trace/shared' +import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events' export interface EdgeFunctionDefinition { env: string[] @@ -691,17 +692,18 @@ function getExtractMetadata(params: { */ if (!dev) { const resource = module.resource - if ( + const hasOGImageGeneration = resource && /[\\/]node_modules[\\/]@vercel[\\/]og[\\/]dist[\\/]index.js$/.test( resource ) - ) { - telemetry.record({ - eventName: 'NEXT_EDGE_IMAGE_GENERATION_USED', - payload: {}, - }) - } + telemetry.record({ + eventName: EVENT_BUILD_FEATURE_USAGE, + payload: { + featureName: 'vercelImageGeneration', + invocationCount: hasOGImageGeneration ? 1 : 0, + }, + }) } /** diff --git a/packages/next/telemetry/events/build.ts b/packages/next/telemetry/events/build.ts index 7d4a7b962c4fb..9ff23eae812ad 100644 --- a/packages/next/telemetry/events/build.ts +++ b/packages/next/telemetry/events/build.ts @@ -153,6 +153,7 @@ export type EventBuildFeatureUsage = { | `swc/target/${SWC_TARGET_TRIPLE}` | 'turbotrace' | 'build-lint' + | 'vercelImageGeneration' invocationCount: number } export function eventBuildFeatureUsage( From 7d218285543a431749fe596b0be493464e759566 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 10 Nov 2022 06:42:38 +0100 Subject: [PATCH 3/5] add test for telemetry --- test/integration/telemetry/pages/api/og.jsx | 9 +++++++++ test/integration/telemetry/test/index.test.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 test/integration/telemetry/pages/api/og.jsx diff --git a/test/integration/telemetry/pages/api/og.jsx b/test/integration/telemetry/pages/api/og.jsx new file mode 100644 index 0000000000000..518c3d654e00d --- /dev/null +++ b/test/integration/telemetry/pages/api/og.jsx @@ -0,0 +1,9 @@ +import { ImageResponse } from '@vercel/og' + +export default async () => { + return new ImageResponse(
hello
) +} + +export const config = { + runtime: 'experimental-edge', +} diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js index 5b2b9d1da6499..17a6bd4b8a93f 100644 --- a/test/integration/telemetry/test/index.test.js +++ b/test/integration/telemetry/test/index.test.js @@ -970,4 +970,19 @@ describe('Telemetry CLI', () => { invocationCount: 1, }) }) + + it('emits telemetry for usage of @vercel/og', async () => { + const { stderr } = await nextBuild(appDir, [], { + stderr: true, + env: { NEXT_TELEMETRY_DEBUG: 1 }, + }) + const featureUsageEvents = findAllTelemetryEvents( + stderr, + 'NEXT_BUILD_FEATURE_USAGE' + ) + expect(featureUsageEvents).toContainEqual({ + featureName: 'vercelImageGeneration', + invocationCount: 1, + }) + }) }) From d18a445ca28cfd247283fadaffc40761da28889b Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 10 Nov 2022 15:31:27 +0100 Subject: [PATCH 4/5] update test snapshot --- test/integration/telemetry/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js index 17a6bd4b8a93f..e8c5f1a301660 100644 --- a/test/integration/telemetry/test/index.test.js +++ b/test/integration/telemetry/test/index.test.js @@ -331,7 +331,7 @@ describe('Telemetry CLI', () => { const event1 = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/.exec(stderr).pop() expect(event1).toMatch(/"staticPropsPageCount": 2/) expect(event1).toMatch(/"serverPropsPageCount": 2/) - expect(event1).toMatch(/"ssrPageCount": 1/) + expect(event1).toMatch(/"ssrPageCount": 2/) expect(event1).toMatch(/"staticPageCount": 4/) expect(event1).toMatch(/"totalPageCount": 9/) }) From 60d217e203b72bb4f009549469740d4289f98871 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Thu, 10 Nov 2022 17:36:48 +0100 Subject: [PATCH 5/5] update test --- test/integration/telemetry/test/index.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js index e8c5f1a301660..6c65320887942 100644 --- a/test/integration/telemetry/test/index.test.js +++ b/test/integration/telemetry/test/index.test.js @@ -333,7 +333,7 @@ describe('Telemetry CLI', () => { expect(event1).toMatch(/"serverPropsPageCount": 2/) expect(event1).toMatch(/"ssrPageCount": 2/) expect(event1).toMatch(/"staticPageCount": 4/) - expect(event1).toMatch(/"totalPageCount": 9/) + expect(event1).toMatch(/"totalPageCount": 10/) }) it('detects isSrcDir dir correctly for `next dev`', async () => {