Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/next-core/src/next_client/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use turbopack_browser::{
};
use turbopack_core::{
chunk::{
ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapsType,
module_id_strategies::ModuleIdStrategy,
ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapSourceType,
SourceMapsType, module_id_strategies::ModuleIdStrategy,
},
compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
environment::{BrowserEnvironment, Environment, ExecutionEnvironment},
Expand Down Expand Up @@ -489,7 +489,7 @@ pub async fn get_client_chunking_context(
if next_mode.is_development() {
builder = builder
.hot_module_replacement()
.use_file_source_map_uris()
.source_map_source_type(SourceMapSourceType::AbsoluteFileUri)
.dynamic_chunk_content_loading(true);
} else {
builder = builder
Expand Down
8 changes: 5 additions & 3 deletions crates/next-core/src/next_server/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use turbopack::{
};
use turbopack_core::{
chunk::{
ChunkingConfig, MangleType, MinifyType, SourceMapsType,
ChunkingConfig, MangleType, MinifyType, SourceMapSourceType, SourceMapsType,
module_id_strategies::ModuleIdStrategy,
},
compile_time_defines,
Expand Down Expand Up @@ -1058,9 +1058,10 @@ pub async fn get_server_chunking_context_with_client_assets(
.debug_ids(*debug_ids.await?);

if next_mode.is_development() {
builder = builder.use_file_source_map_uris();
builder = builder.source_map_source_type(SourceMapSourceType::AbsoluteFileUri);
} else {
builder = builder
.source_map_source_type(SourceMapSourceType::RelativeUri)
.chunking_config(
Vc::<EcmascriptChunkType>::default().to_resolved().await?,
ChunkingConfig {
Expand Down Expand Up @@ -1139,9 +1140,10 @@ pub async fn get_server_chunking_context(
.debug_ids(*debug_ids.await?);

if next_mode.is_development() {
builder = builder.use_file_source_map_uris()
builder = builder.source_map_source_type(SourceMapSourceType::AbsoluteFileUri);
} else {
builder = builder
.source_map_source_type(SourceMapSourceType::RelativeUri)
.chunking_config(
Vc::<EcmascriptChunkType>::default().to_resolved().await?,
ChunkingConfig {
Expand Down
12 changes: 10 additions & 2 deletions packages/next/src/server/patch-error-inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,17 @@ function getSourcemappedFrameIfPossible(
}
sourceMapPayload = maybeSourceMapPayload
try {
// Pass the source map URL as the second parameter so that the consumer
// can resolve relative paths in the source map's `sources` array.
// This is important for Turbopack-generated source maps which use relative paths.
// For most bundlers (Turbopack, Webpack), the source map is in a file with .map extension.
// We don't have access to the actual sourceMappingURL from Node's findSourceMap API,
// but this heuristic works for standard bundler output.
const sourceMapURL = sourceURL + '.map'
sourceMapConsumer = new SyncSourceMapConsumer(
// @ts-expect-error -- Module.SourceMap['version'] is number but SyncSourceMapConsumer wants a string
sourceMapPayload
sourceMapPayload,
/** @ts-expect-error */
sourceMapURL
)
} catch (cause) {
// We should not log an actual error instance here because that will re-enter
Expand Down

Large diffs are not rendered by default.

15 changes: 7 additions & 8 deletions test/e2e/app-dir/server-source-maps/server-source-maps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ function normalizeCliOutput(output: string) {
stripAnsi(output)
// TODO(veil): Should not appear in sourcemapped stackframes.
.replaceAll('webpack:///', 'bundler:///')
.replaceAll('turbopack:///[project]/', 'bundler:///')
.replaceAll(/at [a-zA-Z] \(/g, 'at <mangled> (')
)
}
Expand Down Expand Up @@ -58,7 +57,7 @@ describe('app-dir - server source maps', () => {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(bundler:///app/rsc-error-log/page.js:4:17)'
'(../app/rsc-error-log/page.js:4:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
Expand Down Expand Up @@ -105,12 +104,12 @@ describe('app-dir - server source maps', () => {
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
// TODO(veil): relative paths in webpack
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(bundler:///app/rsc-error-log-cause/page.js:2:17)'
'(app/rsc-error-log-cause/page.js:2:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(bundler:///app/rsc-error-log-cause/page.js:7:17)'
'(app/rsc-error-log-cause/page.js:7:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
Expand Down Expand Up @@ -231,7 +230,7 @@ describe('app-dir - server source maps', () => {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(bundler:///app/ssr-error-log-ignore-listed/page.js:9:17)'
'(app/ssr-error-log-ignore-listed/page.js:9:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'\n' +
Expand Down Expand Up @@ -296,7 +295,7 @@ describe('app-dir - server source maps', () => {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'at <unknown> (bundler:///app/rsc-error-log-ignore-listed/page.js:8:17)'
'at <unknown> (../app/rsc-error-log-ignore-listed/page.js:8:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
Expand Down Expand Up @@ -529,7 +528,7 @@ describe('app-dir - server source maps', () => {
'' +
'\nError: module-evaluation' +
// TODO(veil): Turbopack internals. Feel free to update. Tracked in https://linear.app/vercel/issue/NEXT-4362
'\n at module evaluation (bundler:///app/module-evaluation/module.js:1:22)'
'\n at module evaluation (../app/module-evaluation/module.js:1:22)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('build-output-prerender', () => {
// TODO(veil): Why is the location incomplete unless we enable --no-mangling?
expect(getPrerenderOutput(next.cliOutput)).toMatchInlineSnapshot(`
"Error: Route "/client" used \`new Date()\` inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time-client
at c (bundler:///app/client/page.tsx:4:28)
at c (app/client/page.tsx:4:28)
2 |
3 | export default function Page() {
> 4 | return <p>Current time: {new Date().toISOString()}</p>
Expand Down Expand Up @@ -185,7 +185,7 @@ describe('build-output-prerender', () => {
it('shows all prerender errors with readable stacks and code frames', async () => {
expect(getPrerenderOutput(next.cliOutput)).toMatchInlineSnapshot(`
"Error: Route "/client" used \`new Date()\` inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time-client
at Page (bundler:///app/client/page.tsx:4:28)
at Page (../../../../app/client/page.tsx:4:28)
2 |
3 | export default function Page() {
> 4 | return <p>Current time: {new Date().toISOString()}</p>
Expand All @@ -195,7 +195,7 @@ describe('build-output-prerender', () => {
To get a more detailed stack trace and pinpoint the issue, start the app in development mode by running \`next dev\`, then open "/client" in your browser to investigate the error.
Error occurred prerendering page "/client". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Route "/server" used \`Math.random()\` before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random
at Page (bundler:///app/server/page.tsx:13:27)
at Page (../../../../app/server/page.tsx:13:27)
11 | await cachedDelay()
12 |
> 13 | return <p>Random: {Math.random()}</p>
Expand Down
33 changes: 11 additions & 22 deletions turbopack/crates/turbo-tasks-fs/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
io::{self, ErrorKind},
path::Path,
path::{Path, PathBuf},
};

use anyhow::{Context, Result, anyhow};
Expand All @@ -19,10 +19,7 @@ pub fn extract_disk_access<T>(value: io::Result<T>, path: &Path) -> Result<Optio
}
}

#[cfg(not(target_os = "windows"))]
pub async fn uri_from_file(root: FileSystemPath, path: Option<&str>) -> Result<String> {
use turbo_unix_path::sys_to_unix;

let root_fs = root.fs;
let root_fs = &*ResolvedVc::try_downcast_type::<DiskFileSystem>(root_fs)
.context("Expected root to have a DiskFileSystem")?
Expand All @@ -33,32 +30,26 @@ pub async fn uri_from_file(root: FileSystemPath, path: Option<&str>) -> Result<S
None => root,
};

let sys_path = root_fs.to_sys_path(&path);
Ok(uri_from_path_buf(root_fs.to_sys_path(&path)))
}

#[cfg(not(target_os = "windows"))]
pub fn uri_from_path_buf(sys_path: PathBuf) -> String {
use turbo_unix_path::sys_to_unix;
let sys_path = sys_path.to_string_lossy();

Ok(format!(
format!(
"file://{}",
sys_to_unix(&sys_path)
.split('/')
.map(|s| urlencoding::encode(s))
.collect::<Vec<_>>()
.join("/")
))
)
}

#[cfg(target_os = "windows")]
pub async fn uri_from_file(root: FileSystemPath, path: Option<&str>) -> Result<String> {
let root_fs = root.fs;
let root_fs = &*ResolvedVc::try_downcast_type::<DiskFileSystem>(root_fs)
.context("Expected root to have a DiskFileSystem")?
.await?;

let sys_path = match path {
Some(path) => root.join(path.into())?,
None => root,
};
let sys_path = root_fs.to_sys_path(&sys_path);

pub fn uri_from_path_buf(sys_path: PathBuf) -> String {
let raw_path = sys_path.to_string_lossy().to_string();
let normalized_path = raw_path.replace('\\', "/");

Expand All @@ -70,7 +61,5 @@ pub async fn uri_from_file(root: FileSystemPath, path: Option<&str>) -> Result<S
.collect::<Vec<_>>()
.join("/");

let uri = format!("file:///{}", encoded_path);

Ok(uri)
format!("file:///{}", encoded_path)
}
16 changes: 8 additions & 8 deletions turbopack/crates/turbopack-browser/src/chunking_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use turbopack_core::{
chunk::{
Chunk, ChunkGroupResult, ChunkItem, ChunkType, ChunkableModule, ChunkingConfig,
ChunkingConfigs, ChunkingContext, EntryChunkGroupResult, EvaluatableAsset,
EvaluatableAssets, MinifyType, ModuleId, SourceMapsType,
EvaluatableAssets, MinifyType, ModuleId, SourceMapSourceType, SourceMapsType,
availability_info::AvailabilityInfo,
chunk_group::{MakeChunkGroupResult, make_chunk_group},
module_id_strategies::{DevModuleIdStrategy, ModuleIdStrategy},
Expand Down Expand Up @@ -91,8 +91,8 @@ impl BrowserChunkingContextBuilder {
self
}

pub fn use_file_source_map_uris(mut self) -> Self {
self.chunking_context.should_use_file_source_map_uris = true;
pub fn source_map_source_type(mut self, source_map_source_type: SourceMapSourceType) -> Self {
self.chunking_context.source_map_source_type = source_map_source_type;
self
}

Expand Down Expand Up @@ -229,8 +229,8 @@ pub struct BrowserChunkingContext {
name: Option<RcStr>,
/// The root path of the project
root_path: FileSystemPath,
/// Whether to write file sources as file:// paths in source maps
should_use_file_source_map_uris: bool,
/// The strategy to use for generating source map source uris
source_map_source_type: SourceMapSourceType,
/// This path is used to compute the url to request chunks from
output_root: FileSystemPath,
/// The relative path from the output_root to the root_path.
Expand Down Expand Up @@ -311,7 +311,7 @@ impl BrowserChunkingContext {
client_root,
client_roots: Default::default(),
chunk_root_path,
should_use_file_source_map_uris: false,
source_map_source_type: SourceMapSourceType::TurbopackUriScheme,
asset_root_path,
asset_root_paths: Default::default(),
chunk_base_path: None,
Expand Down Expand Up @@ -617,8 +617,8 @@ impl ChunkingContext for BrowserChunkingContext {
}

#[turbo_tasks::function]
fn should_use_file_source_map_uris(&self) -> Vc<bool> {
Vc::cell(self.should_use_file_source_map_uris)
fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
self.source_map_source_type.cell()
}

#[turbo_tasks::function]
Expand Down
6 changes: 4 additions & 2 deletions turbopack/crates/turbopack-cli/src/dev/web_entry_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use turbo_tasks_fs::FileSystemPath;
use turbopack_browser::{BrowserChunkingContext, react_refresh::assert_can_resolve_react_refresh};
use turbopack_cli_utils::runtime_entry::{RuntimeEntries, RuntimeEntry};
use turbopack_core::{
chunk::{ChunkableModule, ChunkingContext, EvaluatableAsset, SourceMapsType},
chunk::{
ChunkableModule, ChunkingContext, EvaluatableAsset, SourceMapSourceType, SourceMapsType,
},
environment::Environment,
file_source::FileSource,
module::Module,
Expand Down Expand Up @@ -51,7 +53,7 @@ pub async fn get_client_chunking_context(
RuntimeType::Development,
)
.hot_module_replacement()
.use_file_source_map_uris()
.source_map_source_type(SourceMapSourceType::AbsoluteFileUri)
.dynamic_chunk_content_loading(true)
.build(),
))
Expand Down
66 changes: 64 additions & 2 deletions turbopack/crates/turbopack-core/src/chunk/chunking_context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::Result;
use anyhow::{Result, bail};
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
use turbo_rcstr::RcStr;
Expand Down Expand Up @@ -133,13 +133,22 @@ pub struct ChunkingConfig {
#[turbo_tasks::value(transparent)]
pub struct ChunkingConfigs(FxHashMap<ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig>);

#[turbo_tasks::value(shared)]
#[derive(Debug, Clone, Copy, Hash, TaskInput, Default)]
pub enum SourceMapSourceType {
AbsoluteFileUri,
RelativeUri,
#[default]
TurbopackUriScheme,
}

/// A context for the chunking that influences the way chunks are created
#[turbo_tasks::value_trait]
pub trait ChunkingContext {
#[turbo_tasks::function]
fn name(self: Vc<Self>) -> Vc<RcStr>;
#[turbo_tasks::function]
fn should_use_file_source_map_uris(self: Vc<Self>) -> Vc<bool>;
fn source_map_source_type(self: Vc<Self>) -> Vc<SourceMapSourceType>;
/// The root path of the project
#[turbo_tasks::function]
fn root_path(self: Vc<Self>) -> Vc<FileSystemPath>;
Expand Down Expand Up @@ -385,6 +394,13 @@ pub trait ChunkingContextExt {
) -> Vc<OutputAssetsWithReferenced>
where
Self: Send;

/// Computes the relative path from the chunk output root to the project root.
///
/// This is used to compute relative paths for source maps in certain configurations.
fn relative_path_from_chunk_root_to_project_root(self: Vc<Self>) -> Vc<RcStr>
where
Self: Send;
}

impl<T: ChunkingContext + Send + Upcast<Box<dyn ChunkingContext>>> ChunkingContextExt for T {
Expand Down Expand Up @@ -499,6 +515,52 @@ impl<T: ChunkingContext + Send + Upcast<Box<dyn ChunkingContext>>> ChunkingConte
availability_info,
)
}

fn relative_path_from_chunk_root_to_project_root(self: Vc<Self>) -> Vc<RcStr> {
relative_path_from_chunk_root_to_project_root(Vc::upcast_non_strict(self))
}
}

#[turbo_tasks::function]
async fn relative_path_from_chunk_root_to_project_root(
chunking_context: Vc<Box<dyn ChunkingContext>>,
) -> Result<Vc<RcStr>> {
// Example,
// project root: /project/root
// output root: /project/root/dist
// chunk root path: /project/root/dist/ssr/chunks
// output_root_to_chunk_root: ../
//
// Example2,
// project root: /project/root
// output root: /project/out
// chunk root path: /project/out/ssr/chunks
// output_root_to_chunk_root: ../root
//
// From that we want to return ../../../root to get from a path in `chunks` to a path in the
// project root.

let chunk_root_path = chunking_context.chunk_root_path().await?;
let output_root = chunking_context.output_root().await?;
let chunk_to_output_root = chunk_root_path.get_relative_path_to(&output_root);
let Some(chunk_to_output_root) = chunk_to_output_root else {
bail!(
"expected chunk_root_path: {chunk_root_path} to be inside of output_root: \
{output_root}",
chunk_root_path = chunk_root_path.value_to_string().await?,
output_root = output_root.value_to_string().await?
);
};
let output_root_to_chunk_root_path = chunking_context.output_root_to_root_path().await?;

// Note we cannot use `normalize_path` here since it rejects paths that start with `../`
Ok(Vc::cell(
format!(
"{}/{}",
chunk_to_output_root, output_root_to_chunk_root_path
)
.into(),
))
}

#[turbo_tasks::function]
Expand Down
Loading
Loading