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

Add telemetry for @next/font #42579

Merged
merged 7 commits into from Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
21 changes: 20 additions & 1 deletion packages/next/build/webpack/plugins/telemetry-plugin.ts
Expand Up @@ -24,6 +24,8 @@ export type Feature =
| 'next/legacy/image'
| 'next/script'
| 'next/dynamic'
| '@next/font/google'
| '@next/font/local'
| 'swcLoader'
| 'swcMinify'
| 'swcRelay'
Expand Down Expand Up @@ -64,6 +66,10 @@ const FEATURE_MODULE_MAP: ReadonlyMap<Feature, string> = new Map([
['next/script', '/next/script.js'],
['next/dynamic', '/next/dynamic.js'],
])
const FEATURE_MODULE_REGEXP_MAP: ReadonlyMap<Feature, RegExp> = new Map([
['@next/font/google', /\/@next\/font\/google\/target.css?.+$/],
['@next/font/local', /\/@next\/font\/local\/target.css?.+$/],
])

// List of build features used in webpack configuration
const BUILD_FEATURES: Array<Feature> = [
Expand Down Expand Up @@ -101,8 +107,14 @@ function findFeatureInModule(module: Module): Feature | undefined {
if (module.type !== 'javascript/auto') {
return
}
const normalizedIdentifier = module.identifier().replace(/\\/g, '/')
for (const [feature, path] of FEATURE_MODULE_MAP) {
if (module.identifier().replace(/\\/g, '/').endsWith(path)) {
if (normalizedIdentifier.endsWith(path)) {
return feature
}
}
for (const [feature, regexp] of FEATURE_MODULE_REGEXP_MAP) {
if (regexp.test(normalizedIdentifier)) {
return feature
}
}
Expand Down Expand Up @@ -152,6 +164,13 @@ export class TelemetryPlugin implements webpack.WebpackPluginInstance {
invocationCount: 0,
})
}

for (const featureName of FEATURE_MODULE_REGEXP_MAP.keys()) {
this.usageTracker.set(featureName, {
featureName,
invocationCount: 0,
})
}
}

apply(compiler: webpack.Compiler): void {
Expand Down
2 changes: 2 additions & 0 deletions packages/next/telemetry/events/build.ts
Expand Up @@ -136,6 +136,8 @@ export type EventBuildFeatureUsage = {
| 'next/future/image'
| 'next/script'
| 'next/dynamic'
| '@next/font/google'
| '@next/font/local'
| 'experimental/optimizeCss'
| 'experimental/nextScriptWorkers'
| 'optimizeFonts'
Expand Down
96 changes: 96 additions & 0 deletions test/e2e/next-font/telemetry.test.ts
@@ -0,0 +1,96 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { findAllTelemetryEvents } from 'next-test-utils'
import { join } from 'path'

const mockedGoogleFontResponses = require.resolve(
'./google-font-mocked-responses.js'
)

describe('@next/font used telemetry', () => {
const isNextStart = (global as any).isNextStart
let next: NextInstance

if (!isNextStart) {
it('should only run when isNextStart', () => {})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the suite is only testing production behavior it can go in test/production instead.

return
}

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(join(__dirname, 'telemetry/pages')),
'next.config.js': new FileRef(
join(__dirname, 'basepath/next.config.js')
),
},
dependencies: {
'@next/font': 'canary',
},
env: {
NEXT_FONT_GOOGLE_MOCKED_RESPONSES: mockedGoogleFontResponses,
NEXT_TELEMETRY_DEBUG: '1',
},
})
})
afterAll(() => next.destroy())

it('should send @next/font/google and @next/font/local usage event', async () => {
const events = findAllTelemetryEvents(
next.cliOutput,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(events).toContainEqual({
featureName: '@next/font/google',
invocationCount: 1,
})
expect(events).toContainEqual({
featureName: '@next/font/local',
invocationCount: 1,
})
})
})

describe('@next/font unused telemetry', () => {
const isNextStart = (global as any).isNextStart
let next: NextInstance

if (!isNextStart) {
it('should only run when isNextStart', () => {})
return
}

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(join(__dirname, 'telemetry/pages-unused')),
'next.config.js': new FileRef(
join(__dirname, 'basepath/next.config.js')
),
},
dependencies: {
'@next/font': 'canary',
},
env: {
NEXT_FONT_GOOGLE_MOCKED_RESPONSES: mockedGoogleFontResponses,
NEXT_TELEMETRY_DEBUG: '1',
},
})
})
afterAll(() => next.destroy())

it('should send @next/font/google and @next/font/local usage event', async () => {
const events = findAllTelemetryEvents(
next.cliOutput,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(events).toContainEqual({
featureName: '@next/font/google',
invocationCount: 0,
})
expect(events).toContainEqual({
featureName: '@next/font/local',
invocationCount: 0,
})
})
})
13 changes: 13 additions & 0 deletions test/e2e/next-font/telemetry/next.config.js
@@ -0,0 +1,13 @@
module.exports = {
experimental: {
fontLoaders: [
{
loader: '@next/font/google',
options: { subsets: ['latin'] },
},
{
loader: '@next/font/local',
},
],
},
}
3 changes: 3 additions & 0 deletions test/e2e/next-font/telemetry/pages-unused/index.js
@@ -0,0 +1,3 @@
export default function Page() {
return <p>Hello world</p>
}
13 changes: 13 additions & 0 deletions test/e2e/next-font/telemetry/pages/_app.js
@@ -0,0 +1,13 @@
import localFont from '@next/font/local'

const myFont = localFont({ src: './my-font.woff2' })

function MyApp({ Component, pageProps }) {
return (
<div className={myFont.className}>
<Component {...pageProps} />
</div>
)
}

export default MyApp
11 changes: 11 additions & 0 deletions test/e2e/next-font/telemetry/pages/index.js
@@ -0,0 +1,11 @@
import { Open_Sans } from '@next/font/google'
const openSans = Open_Sans({ subsets: ['latin'] })

export default function Page() {
return (
<>
<p>Hello world 1</p>
<p className={openSans.className}>Hello world 2</p>
</>
)
}
Binary file added test/e2e/next-font/telemetry/pages/my-font.woff2
Binary file not shown.
54 changes: 29 additions & 25 deletions test/integration/telemetry/test/index.test.js
Expand Up @@ -11,6 +11,7 @@ import {
nextBuild,
nextLint,
check,
findAllTelemetryEvents,
} from 'next-test-utils'

const appDir = path.join(__dirname, '..')
Expand Down Expand Up @@ -672,7 +673,10 @@ describe('Telemetry CLI', () => {
expect(event1).toMatch(`"nextRulesEnabled": {`)
expect(event1).toMatch(/"@next\/next\/.+?": "(off|warn|error)"/)

const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const featureUsageEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(featureUsageEvents).toContainEqual({
featureName: 'build-lint',
invocationCount: 1,
Expand All @@ -684,7 +688,7 @@ describe('Telemetry CLI', () => {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const events = findAllTelemetryEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'build-lint',
invocationCount: 0,
Expand All @@ -703,7 +707,7 @@ describe('Telemetry CLI', () => {
})
await fs.remove(nextConfig)

const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const events = findAllTelemetryEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'build-lint',
invocationCount: 0,
Expand Down Expand Up @@ -742,7 +746,10 @@ describe('Telemetry CLI', () => {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const featureUsageEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(featureUsageEvents).toEqual(
expect.arrayContaining([
{
Expand Down Expand Up @@ -782,7 +789,10 @@ describe('Telemetry CLI', () => {
})
await fs.remove(path.join(appDir, 'next.config.js'))
await fs.remove(path.join(appDir, 'jsconfig.json'))
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const featureUsageEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(featureUsageEvents).toEqual(
expect.arrayContaining([
{
Expand Down Expand Up @@ -837,7 +847,7 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'next.config.optimize-css')
)

const events = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const events = findAllTelemetryEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
expect(events).toContainEqual({
featureName: 'experimental/optimizeCss',
invocationCount: 1,
Expand All @@ -860,7 +870,10 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'next.config.next-script-workers')
)

const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const featureUsageEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(featureUsageEvents).toContainEqual({
featureName: 'experimental/nextScriptWorkers',
invocationCount: 1,
Expand All @@ -880,7 +893,10 @@ describe('Telemetry CLI', () => {

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

const buildOptimizedEvents = findAllEvents(stderr, 'NEXT_BUILD_OPTIMIZED')
const buildOptimizedEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_OPTIMIZED'
)
expect(buildOptimizedEvents).toContainEqual(
expect.objectContaining({
middlewareCount: 1,
Expand Down Expand Up @@ -917,7 +933,7 @@ describe('Telemetry CLI', () => {
path.join(appDir, 'package.swc-plugins')
)

const pluginDetectedEvents = findAllEvents(
const pluginDetectedEvents = findAllTelemetryEvents(
stderr,
'NEXT_SWC_PLUGIN_DETECTED'
)
Expand All @@ -941,7 +957,10 @@ describe('Telemetry CLI', () => {
stderr: true,
env: { NEXT_TELEMETRY_DEBUG: 1 },
})
const featureUsageEvents = findAllEvents(stderr, 'NEXT_BUILD_FEATURE_USAGE')
const featureUsageEvents = findAllTelemetryEvents(
stderr,
'NEXT_BUILD_FEATURE_USAGE'
)
expect(featureUsageEvents).toContainEqual({
featureName: 'next/legacy/image',
invocationCount: 1,
Expand All @@ -952,18 +971,3 @@ describe('Telemetry CLI', () => {
})
})
})

/**
* 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)
}
15 changes: 15 additions & 0 deletions test/lib/next-test-utils.js
Expand Up @@ -852,3 +852,18 @@ export function runDevSuite(suiteName, appDir, options) {
export function runProdSuite(suiteName, appDir, options) {
return runSuite(suiteName, { appDir, env: 'prod' }, options)
}

/**
* Parse the output and return all entries that match the provided `eventName`
* @param {string} output output of the console
* @param {string} eventName
* @returns {Array<{}>}
*/
export function findAllTelemetryEvents(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)
}