Improve webpack loader error messages and handling (#89698)
## What?
This PR improves error reporting for webpack loaders by:
1. **Preserving loader paths in error messages**: Modified the webpack
message formatter to rewrite "Module build failed" headers to preserve
the loader path information while removing the verbose prefix. This
makes errors more readable in both CLI output and error overlays.
2. **Better error wrapping for non-Error throws**: When loaders throw
non-Error values (strings, objects, etc.), the error is now wrapped with
loader path information in the stack trace, making it easier to identify
which loader caused the error.
3. **Catching deferred loader errors**: Added handling for errors that
occur after loader completion (e.g., unhandled Promise rejections,
setTimeout throws) by:
- Adding an `unhandledRejection` handler in the IPC layer
- Delaying resolution in webpack-loaders transform to allow error
handlers to fire before sending the 'end' message
4. **Conditional `(from ...)` annotation**: The `(from
/path/to/loader.js)` annotation is only added when the loader path is
**not already visible** in the error's stack trace. This avoids
redundant noise for errors that already include the loader in their
stack, while still providing the annotation for errors without loader
context (string throws, errors without stacks, etc.).
5. **Hiding internal Next.js loader paths**: Internal Next.js loader
paths (under `next/dist/`) are filtered from error messages since
they're noise for users. Only user-authored loader paths are shown.
6. **Comprehensive test coverage**: Added e2e tests covering various
error scenarios (Error throws, string throws, Promise rejections,
setTimeout errors, no-stack errors, filesystem errors) with verification
of both CLI output and error overlay display.
## Why?
Webpack loader errors were previously difficult to debug because:
- The verbose "Module build failed" prefix obscured the actual error
message
- Non-Error throws lost their context and loader information
- Deferred errors (Promise rejections, async throws) were not properly
surfaced
- Error overlays didn't clearly show which loader caused the problem
- The `(from ...)` annotation was always added, even when the loader
path was already in the stack trace (addressed per review feedback)
## How?
- **format-webpack-messages.ts**: Changed from filtering out loader
headers to rewriting them, preserving the `(from ./loaders/...)`
information. Internal Next.js loader paths are hidden.
- **webpack-loaders.ts**: Added error wrapping for non-Error values,
deferred resolution to catch async errors, and conditional `(from ...)`
annotation that checks `err.stack` before appending. Resolves loader
paths into an array, then checks if any path appears in the stack before
deciding to annotate.
- **ipc/index.ts**: Added `unhandledRejection` handler to surface
Promise-related errors.
- **Test loaders**: `error-loader.js` and `fs-error-loader.js` delegate
to a `create-error.js` helper that creates errors and invokes the loader
callback inside `setTimeout`. This ensures the loader file path is
absent from the stack trace, exercising the conditional `(from ...)`
annotation path. Other loaders (string-error, promise-error,
timeout-error, no-stack-error) test different error scenarios directly.
- **Test suite**: Comprehensive e2e tests verify CLI output and error
overlay for each error type. Turbopack-specific tests cover promise
rejections, timeout errors, no-stack errors, and filesystem errors.
---------
Co-authored-by: Claude <noreply@anthropic.com>