use anyhow::Result;
use bincode::{Decode, Encode};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
use turbo_tasks_fs::FileSystemPath;
use turbopack_browser::BrowserChunkingContext;
use turbopack_core::{
chunk::{
AssetSuffix, ChunkingConfig, ChunkingContext, CrossOrigin, MangleType, MinifyType,
SourceMapsType, UnusedReferences, UrlBehavior, chunk_id_strategy::ModuleIdStrategy,
},
compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment, NodeJsVersion},
free_var_references,
issue::IssueSeverity,
module_graph::binding_usage_info::OptionBindingUsageInfo,
};
use turbopack_css::chunk::CssChunkType;
use turbopack_ecmascript::chunk::EcmascriptChunkType;
use turbopack_node::execution_context::ExecutionContext;
use turbopack_resolve::resolve_options_context::{ResolveOptionsContext, TsConfigHandling};
use crate::{
app_structure::CollectedRootParams,
mode::NextMode,
next_config::NextConfig,
next_font::local::NextFontLocalResolvePlugin,
next_import_map::{get_next_edge_and_server_fallback_import_map, get_next_edge_import_map},
next_server::context::ServerContextType,
next_shared::resolve::{ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin},
util::{
NextRuntime, OptionEnvMap, defines, foreign_code_context_condition,
free_var_references_with_vercel_system_env_warnings, worker_forwarded_globals,
},
};
#[turbo_tasks::function]
async fn next_edge_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
Ok(defines(&*define_env.await?).cell())
}
/// Define variables for the edge runtime can be accessibly globally.
/// See [here](https://github.com/vercel/next.js/blob/160bb99b06e9c049f88e25806fd995f07f4cc7e1/packages/next/src/build/webpack-config.ts#L1715-L1718) how webpack configures it.
#[turbo_tasks::function]
async fn next_edge_free_vars(
project_path: FileSystemPath,
define_env: Vc<OptionEnvMap>,
report_system_env_inlining: Vc<IssueSeverity>,
) -> Result<Vc<FreeVarReferences>> {
Ok(free_var_references!(
..free_var_references_with_vercel_system_env_warnings(
defines(&*define_env.await?),
*report_system_env_inlining.await?
),
Buffer = FreeVarReference::EcmaScriptModule {
request: rcstr!("buffer"),
lookup_path: Some(project_path),
export: Some(rcstr!("Buffer")),
},
)
.cell())
}
#[turbo_tasks::function]
pub async fn get_edge_compile_time_info(
project_path: FileSystemPath,
define_env: Vc<OptionEnvMap>,
node_version: ResolvedVc<NodeJsVersion>,
report_system_env_inlining: Vc<IssueSeverity>,
) -> Result<Vc<CompileTimeInfo>> {
CompileTimeInfo::builder(
Environment::new(ExecutionEnvironment::EdgeWorker(
EdgeWorkerEnvironment { node_version }.resolved_cell(),
))
.to_resolved()
.await?,
)
.defines(next_edge_defines(define_env).to_resolved().await?)
.free_var_references(
next_edge_free_vars(project_path, define_env, report_system_env_inlining)
.to_resolved()
.await?,
)
.cell()
.await
}
#[turbo_tasks::function]
pub async fn get_edge_resolve_options_context(
project_path: FileSystemPath,
ty: ServerContextType,
mode: Vc<NextMode>,
next_config: Vc<NextConfig>,
execution_context: Vc<ExecutionContext>,
collected_root_params: Option<Vc<CollectedRootParams>>,
) -> Result<Vc<ResolveOptionsContext>> {
let next_edge_import_map = get_next_edge_import_map(
project_path.clone(),
ty.clone(),
next_config,
mode,
execution_context,
collected_root_params,
)
.to_resolved()
.await?;
let next_edge_fallback_import_map =
get_next_edge_and_server_fallback_import_map(project_path.clone(), NextRuntime::Edge)
.to_resolved()
.await?;
let mut before_resolve_plugins = vec![ResolvedVc::upcast(
ModuleFeatureReportResolvePlugin::new(project_path.clone())
.to_resolved()
.await?,
)];
if matches!(
ty,
ServerContextType::Pages { .. }
| ServerContextType::AppSSR { .. }
| ServerContextType::AppRSC { .. }
) {
before_resolve_plugins.push(ResolvedVc::upcast(
NextFontLocalResolvePlugin::new(project_path.clone())
.to_resolved()
.await?,
));
};
let after_resolve_plugins = vec![ResolvedVc::upcast(
NextSharedRuntimeResolvePlugin::new(project_path.clone())
.to_resolved()
.await?,
)];
// https://github.com/vercel/next.js/blob/bf52c254973d99fed9d71507a2e818af80b8ade7/packages/next/src/build/webpack-config.ts#L96-L102
let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();
custom_conditions.extend(NextRuntime::Edge.custom_resolve_conditions());
if ty.should_use_react_server_condition() {
custom_conditions.push(rcstr!("react-server"));
};
// Edge runtime is disabled for projects with Cache Components enabled except for Middleware
// but Middleware doesn't have all Next.js APIs so we omit the "next-js" condition for all edge
// entrypoints
let resolve_options_context = ResolveOptionsContext {
enable_node_modules: Some(project_path.root().owned().await?),
enable_edge_node_externals: true,
custom_conditions,
import_map: Some(next_edge_import_map),
fallback_import_map: Some(next_edge_fallback_import_map),
module: true,
browser: true,
after_resolve_plugins,
before_resolve_plugins,
..Default::default()
};
let tsconfig_path = next_config.typescript_tsconfig_path().await?;
let tsconfig_path = project_path.join(
tsconfig_path
.as_ref()
// Fall back to tsconfig only for resolving. This is because we don't want Turbopack to
// resolve tsconfig.json relative to the file being compiled.
.unwrap_or(&rcstr!("tsconfig.json")),
)?;
Ok(ResolveOptionsContext {
enable_typescript: true,
enable_react: true,
enable_mjs_extension: true,
enable_edge_node_externals: true,
custom_extensions: next_config.resolve_extension().owned().await?,
tsconfig_path: TsConfigHandling::Fixed(tsconfig_path),
rules: vec![(
foreign_code_context_condition(next_config, project_path).await?,
resolve_options_context.clone().resolved_cell(),
)],
..resolve_options_context
}
.cell())
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
pub struct EdgeChunkingContextOptions {
pub mode: Vc<NextMode>,
pub root_path: FileSystemPath,
pub node_root: FileSystemPath,
pub output_root_to_root_path: Vc<RcStr>,
pub environment: Vc<Environment>,
pub module_id_strategy: Vc<ModuleIdStrategy>,
pub export_usage: Vc<OptionBindingUsageInfo>,
pub unused_references: Vc<UnusedReferences>,
pub turbo_minify: Vc<bool>,
pub turbo_source_maps: Vc<SourceMapsType>,
pub no_mangling: Vc<bool>,
pub scope_hoisting: Vc<bool>,
pub nested_async_chunking: Vc<bool>,
pub client_root: FileSystemPath,
pub client_static_folder_name: RcStr,
pub asset_prefix: RcStr,
pub css_url_suffix: Vc<Option<RcStr>>,
pub hash_salt: ResolvedVc<RcStr>,
pub cross_origin: Vc<CrossOrigin>,
}
/// Like `get_edge_chunking_context` but all assets are emitted as client assets (so `/_next`)
#[turbo_tasks::function]
pub async fn get_edge_chunking_context_with_client_assets(
options: EdgeChunkingContextOptions,
) -> Result<Vc<Box<dyn ChunkingContext>>> {
let EdgeChunkingContextOptions {
mode,
root_path,
node_root,
output_root_to_root_path,
environment,
module_id_strategy,
export_usage,
unused_references,
turbo_minify,
turbo_source_maps,
no_mangling,
scope_hoisting,
nested_async_chunking,
client_root,
client_static_folder_name,
asset_prefix,
css_url_suffix,
hash_salt,
cross_origin,
} = options;
let cross_origin_loading = *cross_origin.await?;
let output_root = node_root.join("server/edge")?;
let next_mode = mode.await?;
let mut builder = BrowserChunkingContext::builder(
root_path,
output_root.clone(),
output_root_to_root_path.owned().await?,
client_root.clone(),
output_root.join("chunks/ssr")?,
client_root
.join(&client_static_folder_name)?
.join("media")?,
environment.to_resolved().await?,
next_mode.runtime_type(),
)
.asset_base_path(Some(asset_prefix))
.default_url_behavior(UrlBehavior {
suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
static_suffix: css_url_suffix.to_resolved().await?,
})
.minify_type(if *turbo_minify.await? {
MinifyType::Minify {
// React needs deterministic function names to work correctly.
mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
}
} else {
MinifyType::NoMinify
})
.source_maps(*turbo_source_maps.await?)
.cross_origin(cross_origin_loading)
.module_id_strategy(module_id_strategy.to_resolved().await?)
.export_usage(*export_usage.await?)
.unused_references(unused_references.to_resolved().await?)
.hash_salt(hash_salt)
.nested_async_availability(*nested_async_chunking.await?)
.worker_forwarded_globals(worker_forwarded_globals());
if !next_mode.is_development() {
builder = builder
.chunking_config(
Vc::<EcmascriptChunkType>::default().to_resolved().await?,
ChunkingConfig {
min_chunk_size: 20_000,
..Default::default()
},
)
.chunking_config(
Vc::<CssChunkType>::default().to_resolved().await?,
ChunkingConfig {
max_merge_chunk_size: 100_000,
..Default::default()
},
)
.module_merging(*scope_hoisting.await?);
}
Ok(Vc::upcast(builder.build()))
}
// By default, assets are server assets, but the StructuredImageModuleType ones are on the client
#[turbo_tasks::function]
pub async fn get_edge_chunking_context(
options: EdgeChunkingContextOptions,
) -> Result<Vc<Box<dyn ChunkingContext>>> {
let EdgeChunkingContextOptions {
mode,
root_path,
node_root,
output_root_to_root_path,
environment,
module_id_strategy,
export_usage,
unused_references,
turbo_minify,
turbo_source_maps,
no_mangling,
scope_hoisting,
nested_async_chunking,
client_root,
client_static_folder_name,
asset_prefix,
css_url_suffix,
hash_salt,
cross_origin,
} = options;
let cross_origin = *cross_origin.await?;
let css_url_suffix = css_url_suffix.to_resolved().await?;
let output_root = node_root.join("server/edge")?;
let next_mode = mode.await?;
let mut builder = BrowserChunkingContext::builder(
root_path,
output_root.clone(),
output_root_to_root_path.owned().await?,
output_root.clone(),
output_root.join("chunks")?,
output_root.join("assets")?,
environment.to_resolved().await?,
next_mode.runtime_type(),
)
.client_roots_override(rcstr!("client"), client_root.clone())
.asset_root_path_override(
rcstr!("client"),
client_root
.join(&client_static_folder_name)?
.join("media")?,
)
.asset_base_path_override(rcstr!("client"), asset_prefix)
.url_behavior_override(
rcstr!("client"),
UrlBehavior {
suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
static_suffix: css_url_suffix,
},
)
.default_url_behavior(UrlBehavior {
suffix: AssetSuffix::Inferred,
static_suffix: ResolvedVc::cell(None),
})
// Since one can't read files in edge directly, any asset need to be fetched
// instead. This special blob url is handled by the custom fetch
// implementation in the edge sandbox. It will respond with the
// asset from the output directory.
.asset_base_path(Some(rcstr!("blob:server/edge/")))
.minify_type(if *turbo_minify.await? {
MinifyType::Minify {
mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
}
} else {
MinifyType::NoMinify
})
.source_maps(*turbo_source_maps.await?)
.cross_origin(cross_origin)
.module_id_strategy(module_id_strategy.to_resolved().await?)
.export_usage(*export_usage.await?)
.unused_references(unused_references.to_resolved().await?)
.hash_salt(hash_salt)
.nested_async_availability(*nested_async_chunking.await?)
.worker_forwarded_globals(worker_forwarded_globals());
if !next_mode.is_development() {
builder = builder
.chunking_config(
Vc::<EcmascriptChunkType>::default().to_resolved().await?,
ChunkingConfig {
min_chunk_size: 20_000,
..Default::default()
},
)
.chunking_config(
Vc::<CssChunkType>::default().to_resolved().await?,
ChunkingConfig {
max_merge_chunk_size: 100_000,
..Default::default()
},
)
.module_merging(*scope_hoisting.await?);
}
Ok(Vc::upcast(builder.build()))
}