2022-04-01 17:08:44 +02:00
|
|
|
import {
|
|
|
|
NormalModule,
|
|
|
|
webpack5 as webpack,
|
|
|
|
} from 'next/dist/compiled/webpack/webpack'
|
2021-10-05 21:31:48 +02:00
|
|
|
|
2022-03-22 01:09:03 +01:00
|
|
|
/**
|
|
|
|
* 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 =
|
2021-10-26 09:37:38 +02:00
|
|
|
| 'next/image'
|
2022-07-28 22:03:24 +02:00
|
|
|
| 'next/future/image'
|
2021-10-26 09:37:38 +02:00
|
|
|
| 'next/script'
|
|
|
|
| 'next/dynamic'
|
|
|
|
| 'swcLoader'
|
|
|
|
| 'swcMinify'
|
2022-02-10 02:54:28 +01:00
|
|
|
| 'swcRelay'
|
|
|
|
| 'swcStyledComponents'
|
|
|
|
| 'swcReactRemoveProperties'
|
|
|
|
| 'swcExperimentalDecorators'
|
|
|
|
| 'swcRemoveConsole'
|
|
|
|
| 'swcImportSource'
|
2022-03-15 08:51:15 +01:00
|
|
|
| 'swcEmotion'
|
2022-03-22 01:09:03 +01:00
|
|
|
| `swc/target/${SWC_TARGET_TRIPLE}`
|
2021-10-05 21:31:48 +02:00
|
|
|
|
|
|
|
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'],
|
2022-07-28 22:03:24 +02:00
|
|
|
['next/future/image', '/next/future/image.js'],
|
2021-10-05 21:31:48 +02:00
|
|
|
['next/script', '/next/script.js'],
|
|
|
|
['next/dynamic', '/next/dynamic.js'],
|
|
|
|
])
|
|
|
|
|
2021-10-26 09:37:38 +02:00
|
|
|
// List of build features used in webpack configuration
|
2022-02-10 02:54:28 +01:00
|
|
|
const BUILD_FEATURES: Array<Feature> = [
|
|
|
|
'swcLoader',
|
|
|
|
'swcMinify',
|
|
|
|
'swcRelay',
|
|
|
|
'swcStyledComponents',
|
|
|
|
'swcReactRemoveProperties',
|
|
|
|
'swcExperimentalDecorators',
|
|
|
|
'swcRemoveConsole',
|
|
|
|
'swcImportSource',
|
2022-03-15 08:51:15 +01:00
|
|
|
'swcEmotion',
|
2022-03-22 01:09:03 +01:00
|
|
|
'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',
|
2022-02-10 02:54:28 +01:00
|
|
|
]
|
2021-10-26 09:37:38 +02:00
|
|
|
|
2022-04-01 17:08:44 +02:00
|
|
|
const ELIMINATED_PACKAGES = new Set<string>()
|
|
|
|
|
2021-10-05 21:31:48 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-10-24 23:04:26 +02:00
|
|
|
export class TelemetryPlugin implements webpack.WebpackPluginInstance {
|
2021-10-05 21:31:48 +02:00
|
|
|
private usageTracker = new Map<Feature, FeatureUsage>()
|
|
|
|
|
2021-10-26 09:37:38 +02:00
|
|
|
// 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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-10-05 21:31:48 +02:00
|
|
|
for (const featureName of FEATURE_MODULE_MAP.keys()) {
|
|
|
|
this.usageTracker.set(featureName, {
|
|
|
|
featureName,
|
|
|
|
invocationCount: 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-24 23:04:26 +02:00
|
|
|
apply(compiler: webpack.Compiler): void {
|
2021-10-05 21:31:48 +02:00
|
|
|
compiler.hooks.make.tapAsync(
|
|
|
|
TelemetryPlugin.name,
|
2021-10-24 23:04:26 +02:00
|
|
|
async (compilation: webpack.Compilation, callback: () => void) => {
|
2021-10-05 21:31:48 +02:00
|
|
|
compilation.hooks.finishModules.tapAsync(
|
|
|
|
TelemetryPlugin.name,
|
2021-10-24 23:04:26 +02:00
|
|
|
async (modules: Iterable<Module>, modulesFinish: () => void) => {
|
2021-10-05 21:31:48 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
)
|
2022-04-01 17:08:44 +02:00
|
|
|
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
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-10-05 21:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
usages(): FeatureUsage[] {
|
|
|
|
return [...this.usageTracker.values()]
|
|
|
|
}
|
2022-04-01 17:08:44 +02:00
|
|
|
|
|
|
|
packagesUsedInServerSideProps(): string[] {
|
|
|
|
return Array.from(ELIMINATED_PACKAGES)
|
|
|
|
}
|
2021-10-05 21:31:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
}
|