Skip to content

Commit

Permalink
Collect telemetry for next/future/image (#39046)
Browse files Browse the repository at this point in the history
* test: cleanup telemetry integration test

Instead of constantly parsing the console output, parse the output
once, convert the entries in JavaScript objects, then compare them
logically rather than textually.

* Collect telemetry for next/future/image

Track adoption of next/future/image and add relevant test.
  • Loading branch information
kyliau committed Jul 28, 2022
1 parent ec2f0a8 commit 85b00b2
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 125 deletions.
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)
}

0 comments on commit 85b00b2

Please sign in to comment.