Repository: vercel/next.js
Pull request: bugfix: Fix a bug that caused conflicting assets when adding a child compiler

fixes #76930

## Summary

Fixes an issue where using a Webpack plugin that creates child compilers in a Next.js project could result in asset emission conflicts.

## Description

Starting from [vercel/next.js#75927](https://github.com/vercel/next.js/pull/75927), Next.js uses compilation.emitAsset to emit assets. However, emitAsset throws an error when attempting to emit an asset with the same name and identical content more than once.
This behavior is defined in Webpack's implementation:
https://github.com/webpack/webpack/blob/70e3d9d5dbd3548431b8878dead4647beff5c14f/lib/Compilation.js#L4615-L4628

When a plugin hooks into the compilation phase, it also runs for child compilers. This can lead to duplicated asset emission when using plugins that spawn child compilers, such as [vanilla-extract](https://github.com/vanilla-extract-css/vanilla-extract/blob/c66be53d600802b2922da1d6034e2a5ff3fbbcae/packages/webpack-plugin/src/compiler.ts#L85-L89).

To prevent this, we now use the thisCompilation hook instead of compilation, which ensures that the plugin only applies to the main compilation and not to any child compilers.

Changed files:
filename: packages/next/src/build/webpack/plugins/flight-manifest-plugin.ts
patch: @@ -223,7 +223,7 @@ export class ClientReferenceManifestPlugin {
   }
 
   apply(compiler: webpack.Compiler) {
-    compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+    compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
       compilation.hooks.processAssets.tap(
         {
           name: PLUGIN_NAME,

filename: packages/next/src/build/webpack/plugins/middleware-plugin.ts
patch: @@ -781,7 +781,7 @@ export default class MiddlewarePlugin {
   }
 
   public apply(compiler: webpack.Compiler) {
-    compiler.hooks.compilation.tap(NAME, (compilation, params) => {
+    compiler.hooks.thisCompilation.tap(NAME, (compilation, params) => {
       const { hooks } = params.normalModuleFactory
       /**
        * This is the static code analysis phase.

filename: packages/next/src/build/webpack/plugins/next-trace-entrypoints-plugin.ts
patch: @@ -575,7 +575,7 @@ export class TraceEntryPointsPlugin implements webpack.WebpackPluginInstance {
   }
 
   apply(compiler: webpack.Compiler) {
-    compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+    compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
       const compilationSpan =
         getCompilationSpan(compilation) || getCompilationSpan(compiler)!
       const traceEntrypointsPluginSpan = compilationSpan.traceChild(

filename: packages/next/src/build/webpack/plugins/next-types-plugin/index.ts
patch: @@ -1003,7 +1003,7 @@ export class NextTypesPlugin {
       }
     }
 
-    compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
+    compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
       compilation.hooks.processAssets.tapAsync(
         {
           name: PLUGIN_NAME,

filename: packages/next/src/build/webpack/plugins/profiling-plugin.ts
patch: @@ -91,7 +91,7 @@ export class ProfilingPlugin {
   traceTopLevelHooks(compiler: any) {
     this.traceHookPair(
       'webpack-compilation',
-      compiler.hooks.compilation,
+      compiler.hooks.thisCompilation,
       compiler.hooks.afterCompile,
       {
         parentSpan: () =>
@@ -146,7 +146,7 @@ export class ProfilingPlugin {
       },
     })
 
-    compiler.hooks.compilation.tap(
+    compiler.hooks.thisCompilation.tap(
       { name: pluginName, stage: -Infinity },
       (compilation: any) => {
         compilation.hooks.buildModule.tap(pluginName, (module: any) => {