Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collect telemetry for next/future/image #39046

Merged
merged 3 commits into from Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/build/webpack/plugins/telemetry-plugin.ts
Expand Up @@ -23,6 +23,7 @@ export type SWC_TARGET_TRIPLE =

export type Feature =
| 'next/image'
| 'next/future/image'
| 'next/script'
| 'next/dynamic'
| 'swcLoader'
Expand Down Expand Up @@ -59,6 +60,7 @@ interface Connection {
// Map of a feature module to the file it belongs in the next package.
const FEATURE_MODULE_MAP: ReadonlyMap<Feature, string> = new Map([
['next/image', '/next/image.js'],
['next/future/image', '/next/future/image.js'],
['next/script', '/next/script.js'],
['next/dynamic', '/next/dynamic.js'],
])
Expand Down
1 change: 1 addition & 0 deletions packages/next/telemetry/events/build.ts
Expand Up @@ -132,6 +132,7 @@ export type EventBuildFeatureUsage = {
// *before* you make changes here.
featureName:
| 'next/image'
| 'next/future/image'
| 'next/script'
| 'next/dynamic'
| 'experimental/optimizeCss'
Expand Down
5 changes: 5 additions & 0 deletions test/integration/telemetry/pages/about.js
@@ -1,4 +1,5 @@
import Image from 'next/image'
import { Image as FutureImage } from 'next/future/image'
import profilePic from '../public/small.jpg'

function About() {
Expand All @@ -12,3 +13,7 @@ function About() {
}

export default About

export function AboutFutureImage() {
return <FutureImage src={profilePic} alt="Picture of the author" />
}
258 changes: 133 additions & 125 deletions test/integration/telemetry/test/index.test.js
Expand Up @@ -574,25 +574,23 @@ describe('Telemetry CLI', () => {
expect(event1).toMatch(`"nextRulesEnabled": {`)
expect(event1).toMatch(/"@next\/next\/.+?": "(off|warn|error)"/)

const event2 = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()
expect(event2).toContain(`"featureName": "build-lint"`)
expect(event2).toContain(`"invocationCount": 1`)
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(featureUsageEvents).toContainEqual({
featureName: 'build-lint',
invocationCount: 1,
})
})

it(`emits telemetry for lint during build when '--no-lint' is specified`, async () => {
const { stderr } = await nextBuild(appDir, ['--no-lint'], {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})

const event1 = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()

expect(event1).toContain(`"featureName": "build-lint"`)
expect(event1).toContain(`"invocationCount": 0`)
const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'build-lint',
invocationCount: 0,
})
})

it(`emits telemetry for lint during build when 'ignoreDuringBuilds' is specified`, async () => {
Expand All @@ -607,12 +605,11 @@ describe('Telemetry CLI', () => {
})
await fs.remove(nextConfig)

const event1 = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/
.exec(stderr)
.pop()

expect(event1).toContain(`"featureName": "build-lint"`)
expect(event1).toContain(`"invocationCount": 0`)
const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'build-lint',
invocationCount: 0,
})
})

it('emits telemetry for `next lint`', async () => {
Expand Down Expand Up @@ -642,49 +639,32 @@ describe('Telemetry CLI', () => {
expect(event1).toMatch(/"@next\/next\/.+?": "(off|warn|error)"/)
})

it('emits telemery for usage of image, script & dynamic', async () => {
it('emits telemery for usage of optimizeFonts, image, script & dynamic', async () => {
const { stderr } = await nextBuild(appDir, [], {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
const regex = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/g
regex.exec(stderr).pop() // optimizeCss
regex.exec(stderr).pop() // nextScriptWorkers
regex.exec(stderr).pop() // build-lint
const optimizeFonts = regex.exec(stderr).pop()
expect(optimizeFonts).toContain(`"featureName": "optimizeFonts"`)
expect(optimizeFonts).toContain(`"invocationCount": 1`)
regex.exec(stderr).pop() // swcLoader
regex.exec(stderr).pop() // swcMinify
regex.exec(stderr).pop() // swcRelay
regex.exec(stderr).pop() // swcStyledComponents
regex.exec(stderr).pop() // swcExperimentalDecorators
regex.exec(stderr).pop() // swcReactRemoveProperties
regex.exec(stderr).pop() // swcRemoveConsole
regex.exec(stderr).pop() // swcImportSource
regex.exec(stderr).pop() // swcEmotion
regex.exec(stderr).pop() // swc/targets/*
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
regex.exec(stderr).pop()
const image = regex.exec(stderr).pop()
expect(image).toContain(`"featureName": "next/image"`)
expect(image).toContain(`"invocationCount": 1`)
const script = regex.exec(stderr).pop()
expect(script).toContain(`"featureName": "next/script"`)
expect(script).toContain(`"invocationCount": 1`)
const dynamic = regex.exec(stderr).pop()
expect(dynamic).toContain(`"featureName": "next/dynamic"`)
expect(dynamic).toContain(`"invocationCount": 1`)
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(featureUsageEvents).toEqual(
expect.arrayContaining([
{
featureName: 'optimizeFonts',
invocationCount: 1,
},
{
featureName: 'next/image',
invocationCount: 1,
},
{
featureName: 'next/script',
invocationCount: 1,
},
{
featureName: 'next/dynamic',
invocationCount: 1,
},
])
)
})

it('emits telemetry for usage of swc', async () => {
Expand All @@ -704,42 +684,43 @@ describe('Telemetry CLI', () => {
})
await fs.remove(path.join(appDir, 'next.config.js'))
await fs.remove(path.join(appDir, 'jsconfig.json'))

const regex = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/g
regex.exec(stderr).pop() // optimizeCss
regex.exec(stderr).pop() // nextScriptWorkers
regex.exec(stderr).pop() // build-lint
regex.exec(stderr).pop() // optimizeFonts
const swcLoader = regex.exec(stderr).pop()
expect(swcLoader).toContain(`"featureName": "swcLoader"`)
expect(swcLoader).toContain(`"invocationCount": 1`)
const swcMinify = regex.exec(stderr).pop()
expect(swcMinify).toContain(`"featureName": "swcMinify"`)
expect(swcMinify).toContain(`"invocationCount": 1`)
const swcRelay = regex.exec(stderr).pop()
expect(swcRelay).toContain(`"featureName": "swcRelay"`)
expect(swcRelay).toContain(`"invocationCount": 1`)
const swcStyledComponents = regex.exec(stderr).pop()
expect(swcStyledComponents).toContain(
`"featureName": "swcStyledComponents"`
)
expect(swcStyledComponents).toContain(`"invocationCount": 1`)
const swcReactRemoveProperties = regex.exec(stderr).pop()
expect(swcReactRemoveProperties).toContain(
`"featureName": "swcReactRemoveProperties"`
)
expect(swcReactRemoveProperties).toContain(`"invocationCount": 1`)
const swcExperimentalDecorators = regex.exec(stderr).pop()
expect(swcExperimentalDecorators).toContain(
`"featureName": "swcExperimentalDecorators"`
)
expect(swcExperimentalDecorators).toContain(`"invocationCount": 1`)
const swcRemoveConsole = regex.exec(stderr).pop()
expect(swcRemoveConsole).toContain(`"featureName": "swcRemoveConsole"`)
expect(swcRemoveConsole).toContain(`"invocationCount": 1`)
const swcImportSource = regex.exec(stderr).pop()
expect(swcImportSource).toContain(`"featureName": "swcImportSource"`)
expect(swcImportSource).toContain(`"invocationCount": 0`)
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(featureUsageEvents).toEqual(
expect.arrayContaining([
{
featureName: 'swcLoader',
invocationCount: 1,
},
{
featureName: 'swcMinify',
invocationCount: 1,
},
{
featureName: 'swcRelay',
invocationCount: 1,
},
{
featureName: 'swcStyledComponents',
invocationCount: 1,
},
{
featureName: 'swcReactRemoveProperties',
invocationCount: 1,
},
{
featureName: 'swcExperimentalDecorators',
invocationCount: 1,
},
{
featureName: 'swcRemoveConsole',
invocationCount: 1,
},
{
featureName: 'swcImportSource',
invocationCount: 0,
},
])
)
})

it('emits telemetry for usage of `optimizeCss`', async () => {
Expand All @@ -758,11 +739,11 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'next.config.optimize-css')
)

const regex = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/g
regex.exec(stderr).pop() // build-lint
const optimizeCss = regex.exec(stderr).pop()
expect(optimizeCss).toContain(`"featureName": "experimental/optimizeCss"`)
expect(optimizeCss).toContain(`"invocationCount": 1`)
const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'experimental/optimizeCss',
invocationCount: 1,
})
})

it('emits telemetry for usage of `nextScriptWorkers`', async () => {
Expand All @@ -781,14 +762,11 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'next.config.next-script-workers')
)

const regex = /NEXT_BUILD_FEATURE_USAGE[\s\S]+?{([\s\S]+?)}/g
regex.exec(stderr).pop() // build-lint
regex.exec(stderr).pop() // optimizeCss
const nextScriptWorkers = regex.exec(stderr).pop()
expect(nextScriptWorkers).toContain(
`"featureName": "experimental/nextScriptWorkers"`
)
expect(nextScriptWorkers).toContain(`"invocationCount": 1`)
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(featureUsageEvents).toContainEqual({
featureName: 'experimental/nextScriptWorkers',
invocationCount: 1,
})
})

it('emits telemetry for usage of middleware', async () => {
Expand All @@ -804,9 +782,12 @@ describe('Telemetry CLI', () => {

await fs.remove(path.join(appDir, 'middleware.js'))

const regex = /NEXT_BUILD_OPTIMIZED[\s\S]+?{([\s\S]+?)}/
const optimizedEvt = regex.exec(stderr).pop()
expect(optimizedEvt).toContain(`"middlewareCount": 1`)
const buildOptimizedEvents = findAllEvents(stderr, 'NEXT_BUILD_OPTIMIZED')
expect(buildOptimizedEvents).toContainEqual(
expect.objectContaining({
middlewareCount: 1,
})
)
})

it('emits telemetry for usage of swc plugins', async () => {
Expand All @@ -828,8 +809,6 @@ describe('Telemetry CLI', () => {
env: { NEXT_TELEMETRY_DEBUG: 1 },
})

console.log(stderr)

await fs.rename(
path.join(appDir, 'next.config.js'),
path.join(appDir, 'next.config.swc-plugins')
Expand All @@ -840,20 +819,49 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'package.swc-plugins')
)

const regex = /NEXT_SWC_PLUGIN_DETECTED[\s\S]+?{([\s\S]+?)}/g

const coverage = regex.exec(stderr).pop()
expect(coverage).toContain(`"pluginName": "swc-plugin-coverage-instrument"`)
expect(coverage).toContain(`"pluginVersion": "0.0.6"`)

const relay = regex.exec(stderr).pop()
expect(relay).toContain(`"pluginName": "@swc/plugin-relay"`)
expect(relay).toContain(`"pluginVersion": "0.2.0"`)

const absolute = regex.exec(stderr).pop()
expect(absolute).toContain(
`"pluginName": "/test/absolute_path/plugin.wasm"`
const pluginDetectedEvents = findAllEvents(
stderr,
'NEXT_SWC_PLUGIN_DETECTED'
)
expect(absolute).not.toContain(`pluginVersion`)
expect(pluginDetectedEvents).toEqual([
{
pluginName: 'swc-plugin-coverage-instrument',
pluginVersion: '0.0.6',
},
{
pluginName: '@swc/plugin-relay',
pluginVersion: '0.2.0',
},
{
pluginName: '/test/absolute_path/plugin.wasm',
},
])
})

it('emits telemetry for usage of next/future/image', async () => {
const { stderr } = await nextBuild(appDir, [], {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(featureUsageEvents).toContainEqual({
featureName: 'next/future/image',
invocationCount: 1,
})
})
})

/**
* Parse the output and return all entries that match the provided `eventName`
* @param {string} output output of the console
* @param {string} eventName
* @returns {Array<{}>}
*/
function findAllEvents(output, eventName) {
const regex = /\[telemetry\] ({.+?^})/gms
// Pop the last element of each entry to retrieve contents of the capturing group
const events = [...output.matchAll(regex)].map((entry) =>
JSON.parse(entry.pop())
)
return events.filter((e) => e.eventName === eventName).map((e) => e.payload)
}