rsnext/packages/next/build/webpack/plugins/telemetry-plugin.ts
JJ Kasper ccc8d271df
Revert "edge-ssr: bundle next/dist as ESM for better tree-shaking (#40251) (#40967)
This reverts commit 11deaaa82b.

Temporarily reverts the above commit due to breaking middleware/edge
functions once deployed.

Fixes:
https://github.com/vercel/next.js/actions/runs/3133433920/jobs/5087331787

cc @shuding @feedthejim 

```sh
[GET] /blog/first
13:56:56:61
2022-09-27T20:56:56.671Z	61d43a6a-34a1-40c0-b71f-4ae5d1918431	ERROR	/var/task/node_modules/next/dist/esm/client/router.js:1
/* global window */ import React from 'react';
                    ^^^^^^
SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1033:15)
    at Module._compile (node:internal/modules/cjs/loader:1069:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Module.require (node:internal/modules/cjs/loader:1005:19)
    at require (node:internal/modules/cjs/helpers:102:18)
    at Object.<anonymous> (/var/task/node_modules/next/router.js:3:7)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
```
2022-09-27 15:05:40 -07:00

192 lines
5.4 KiB
TypeScript

import { NormalModule, webpack } from 'next/dist/compiled/webpack/webpack'
/**
* List of target triples next-swc native binary supports.
*/
export type SWC_TARGET_TRIPLE =
| 'x86_64-apple-darwin'
| 'x86_64-unknown-linux-gnu'
| 'x86_64-pc-windows-msvc'
| 'i686-pc-windows-msvc'
| 'aarch64-unknown-linux-gnu'
| 'armv7-unknown-linux-gnueabihf'
| 'aarch64-apple-darwin'
| 'aarch64-linux-android'
| 'arm-linux-androideabi'
| 'x86_64-unknown-freebsd'
| 'x86_64-unknown-linux-musl'
| 'aarch64-unknown-linux-musl'
| 'aarch64-pc-windows-msvc'
export type Feature =
| 'next/image'
| 'next/future/image'
| 'next/script'
| 'next/dynamic'
| 'swcLoader'
| 'swcMinify'
| 'swcRelay'
| 'swcStyledComponents'
| 'swcReactRemoveProperties'
| 'swcExperimentalDecorators'
| 'swcRemoveConsole'
| 'swcImportSource'
| 'swcEmotion'
| `swc/target/${SWC_TARGET_TRIPLE}`
interface FeatureUsage {
featureName: Feature
invocationCount: number
}
/**
* A vertex in the module graph.
*/
interface Module {
type: string
identifier(): string
}
/**
* An edge in the module graph.
*/
interface Connection {
originModule: unknown
}
// 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'],
])
// List of build features used in webpack configuration
const BUILD_FEATURES: Array<Feature> = [
'swcLoader',
'swcMinify',
'swcRelay',
'swcStyledComponents',
'swcReactRemoveProperties',
'swcExperimentalDecorators',
'swcRemoveConsole',
'swcImportSource',
'swcEmotion',
'swc/target/x86_64-apple-darwin',
'swc/target/x86_64-unknown-linux-gnu',
'swc/target/x86_64-pc-windows-msvc',
'swc/target/i686-pc-windows-msvc',
'swc/target/aarch64-unknown-linux-gnu',
'swc/target/armv7-unknown-linux-gnueabihf',
'swc/target/aarch64-apple-darwin',
'swc/target/aarch64-linux-android',
'swc/target/arm-linux-androideabi',
'swc/target/x86_64-unknown-freebsd',
'swc/target/x86_64-unknown-linux-musl',
'swc/target/aarch64-unknown-linux-musl',
'swc/target/aarch64-pc-windows-msvc',
]
const ELIMINATED_PACKAGES = new Set<string>()
/**
* Determine if there is a feature of interest in the specified 'module'.
*/
function findFeatureInModule(module: Module): Feature | undefined {
if (module.type !== 'javascript/auto') {
return
}
for (const [feature, path] of FEATURE_MODULE_MAP) {
if (module.identifier().replace(/\\/g, '/').endsWith(path)) {
return feature
}
}
}
/**
* Find unique origin modules in the specified 'connections', which possibly
* contains more than one connection for a module due to different types of
* dependency.
*/
function findUniqueOriginModulesInConnections(
connections: Connection[]
): Set<unknown> {
const originModules = new Set()
for (const connection of connections) {
if (!originModules.has(connection.originModule)) {
originModules.add(connection.originModule)
}
}
return originModules
}
/**
* Plugin that queries the ModuleGraph to look for modules that correspond to
* certain features (e.g. next/image and next/script) and record how many times
* they are imported.
*/
export class TelemetryPlugin implements webpack.WebpackPluginInstance {
private usageTracker = new Map<Feature, FeatureUsage>()
// Build feature usage is on/off and is known before the build starts
constructor(buildFeaturesMap: Map<Feature, boolean>) {
for (const featureName of BUILD_FEATURES) {
this.usageTracker.set(featureName, {
featureName,
invocationCount: buildFeaturesMap.get(featureName) ? 1 : 0,
})
}
for (const featureName of FEATURE_MODULE_MAP.keys()) {
this.usageTracker.set(featureName, {
featureName,
invocationCount: 0,
})
}
}
apply(compiler: webpack.Compiler): void {
compiler.hooks.make.tapAsync(
TelemetryPlugin.name,
async (compilation: webpack.Compilation, callback: () => void) => {
compilation.hooks.finishModules.tapAsync(
TelemetryPlugin.name,
async (modules: Iterable<Module>, modulesFinish: () => void) => {
for (const module of modules) {
const feature = findFeatureInModule(module)
if (!feature) {
continue
}
const connections = (
compilation as any
).moduleGraph.getIncomingConnections(module)
const originModules =
findUniqueOriginModulesInConnections(connections)
this.usageTracker.get(feature)!.invocationCount =
originModules.size
}
modulesFinish()
}
)
callback()
}
)
if (compiler.options.mode === 'production' && !compiler.watchMode) {
compiler.hooks.compilation.tap(TelemetryPlugin.name, (compilation) => {
const moduleHooks = NormalModule.getCompilationHooks(compilation)
moduleHooks.loader.tap(TelemetryPlugin.name, (loaderContext: any) => {
loaderContext.eliminatedPackages = ELIMINATED_PACKAGES
})
})
}
}
usages(): FeatureUsage[] {
return [...this.usageTracker.values()]
}
packagesUsedInServerSideProps(): string[] {
return Array.from(ELIMINATED_PACKAGES)
}
}