diff --git a/packages/next-swc/crates/napi/src/next_api/project.rs b/packages/next-swc/crates/napi/src/next_api/project.rs index a9d1cb728a34b..1e8eff0c8ff72 100644 --- a/packages/next-swc/crates/napi/src/next_api/project.rs +++ b/packages/next-swc/crates/napi/src/next_api/project.rs @@ -30,12 +30,14 @@ use turbopack_binding::{ }, turbopack::{ core::{ + chunk::ModuleId, diagnostics::PlainDiagnostic, error::PrettyPrintError, issue::PlainIssue, source_map::{GenerateSourceMap, Token}, version::{PartialUpdate, TotalUpdate, Update, VersionState}, }, + dev::ecmascript::EcmascriptDevChunkContent, ecmascript_hmr_protocol::{ClientUpdateInstruction, ResourceIdentifier}, trace_utils::{ exit::ExitGuard, @@ -804,12 +806,16 @@ pub async fn project_trace_source( let turbo_tasks = project.turbo_tasks.clone(); let traced_frame = turbo_tasks .run_once(async move { - let file = match Url::parse(&frame.file) { + let (file, module) = match Url::parse(&frame.file) { Ok(url) => match url.scheme() { - "file" => urlencoding::decode(url.path())?.to_string(), + "file" => { + let path = urlencoding::decode(url.path())?.to_string(); + let module = url.query_pairs().find(|(k, _)| k == "id"); + (path, module.map(|(_, m)| m.into_owned())) + } _ => bail!("Unknown url scheme"), }, - Err(_) => frame.file.to_string(), + Err(_) => (frame.file.to_string(), None), }; let Some(chunk_base) = file.strip_prefix( @@ -837,19 +843,38 @@ pub async fn project_trace_source( .join(chunk_base.to_owned()) }; - let Some(versioned) = Vc::try_resolve_sidecast::>( - project.container.get_versioned_content(path), - ) - .await? - else { - bail!("Could not GenerateSourceMap") + let content_vc = project.container.get_versioned_content(path); + let map = match module { + Some(module) => { + let Some(content) = + Vc::try_resolve_downcast_type::(content_vc) + .await? + else { + bail!("Was not EcmascriptDevChunkContent") + }; + + let entries = content.entries().await?; + let entry = entries.get(&ModuleId::String(module).cell().await?); + let map = match entry { + Some(entry) => *entry.code.generate_source_map().await?, + None => None, + }; + map.context("Entry is missing sourcemap")? + } + None => { + let Some(versioned) = + Vc::try_resolve_sidecast::>(content_vc).await? + else { + bail!("Could not GenerateSourceMap") + }; + + versioned + .generate_source_map() + .await? + .context("Chunk is missing a sourcemap")? + } }; - let map = versioned - .generate_source_map() - .await? - .context("Chunk is missing a sourcemap")?; - let token = map .lookup_token(frame.line as usize, frame.column.unwrap_or(0) as usize) .await? diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/parseStack.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/parseStack.ts index 1a0767318cb55..4292aa950761e 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/parseStack.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/parseStack.ts @@ -14,7 +14,7 @@ export function parseStack(stack: string): StackFrame[] { ?.replace(/\\/g, '/') ?.replace(/\/$/, '') if (distDir) { - frame.file = 'file://' + distDir.concat(res.pop()!) + frame.file = 'file://' + distDir.concat(res.pop()!) + url.search } } } catch {} diff --git a/test/development/acceptance-app/error-recovery.test.ts b/test/development/acceptance-app/error-recovery.test.ts index 3c40e4496065e..745d2aa20e76b 100644 --- a/test/development/acceptance-app/error-recovery.test.ts +++ b/test/development/acceptance-app/error-recovery.test.ts @@ -6,7 +6,7 @@ import path from 'path' import { outdent } from 'outdent' describe.each(['default', 'turbo'])('Error recovery app %s', () => { - const { next } = nextTestSetup({ + const { next, isTurbopack } = nextTestSetup({ files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), dependencies: { react: 'latest', @@ -143,17 +143,32 @@ describe.each(['default', 'turbo'])('Error recovery app %s', () => { ).toBe('1') await session.waitForAndOpenRuntimeError() - expect(await session.getRedboxSource()).toMatchInlineSnapshot(` - "index.js (7:10) @ eval - - 5 | const increment = useCallback(() => { - 6 | setCount(c => c + 1) - > 7 | throw new Error('oops') - | ^ - 8 | }, [setCount]) - 9 | return ( - 10 |
" - `) + + if (isTurbopack) { + expect(await session.getRedboxSource()).toMatchInlineSnapshot(` + "index.js (7:5) @ eval + + 5 | const increment = useCallback(() => { + 6 | setCount(c => c + 1) + > 7 | throw new Error('oops') + | ^ + 8 | }, [setCount]) + 9 | return ( + 10 |
" + `) + } else { + expect(await session.getRedboxSource()).toMatchInlineSnapshot(` + "index.js (7:10) @ eval + + 5 | const increment = useCallback(() => { + 6 | setCount(c => c + 1) + > 7 | throw new Error('oops') + | ^ + 8 | }, [setCount]) + 9 | return ( + 10 |
" + `) + } await session.patch( 'index.js', diff --git a/test/turbopack-tests-manifest.json b/test/turbopack-tests-manifest.json index ed0c2c79faaa4..65f65e956aef7 100644 --- a/test/turbopack-tests-manifest.json +++ b/test/turbopack-tests-manifest.json @@ -1084,6 +1084,7 @@ }, "test/development/acceptance-app/error-recovery.test.ts": { "passed": [ + "Error recovery app turbo can recover from a event handler error", "Error recovery app turbo can recover from a syntax error without losing state", "Error recovery app turbo client component can recover from a component error", "Error recovery app turbo displays build error on initial page load", @@ -1092,7 +1093,6 @@ "Error recovery app turbo syntax > runtime error" ], "failed": [ - "Error recovery app turbo can recover from a event handler error", "Error recovery app turbo client component can recover from syntax error", "Error recovery app turbo server component can recover from a component error", "Error recovery app turbo stuck error"