1use anyhow::Result;
2use bincode::{Decode, Encode};
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
5use turbo_tasks_fs::FileSystemPath;
6use turbopack_browser::BrowserChunkingContext;
7use turbopack_core::{
8 chunk::{
9 AssetSuffix, ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapsType,
10 UnusedReferences, UrlBehavior, chunk_id_strategy::ModuleIdStrategy,
11 },
12 compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
13 environment::{EdgeWorkerEnvironment, Environment, ExecutionEnvironment, NodeJsVersion},
14 free_var_references,
15 issue::IssueSeverity,
16 module_graph::binding_usage_info::OptionBindingUsageInfo,
17};
18use turbopack_css::chunk::CssChunkType;
19use turbopack_ecmascript::chunk::EcmascriptChunkType;
20use turbopack_node::execution_context::ExecutionContext;
21use turbopack_resolve::resolve_options_context::{ResolveOptionsContext, TsConfigHandling};
22
23use crate::{
24 app_structure::CollectedRootParams,
25 mode::NextMode,
26 next_config::NextConfig,
27 next_font::local::NextFontLocalResolvePlugin,
28 next_import_map::{get_next_edge_and_server_fallback_import_map, get_next_edge_import_map},
29 next_server::context::ServerContextType,
30 next_shared::resolve::{ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin},
31 util::{
32 NextRuntime, OptionEnvMap, defines, foreign_code_context_condition,
33 free_var_references_with_vercel_system_env_warnings, worker_forwarded_globals,
34 },
35};
36
37#[turbo_tasks::function]
38async fn next_edge_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
39 Ok(defines(&*define_env.await?).cell())
40}
41
42#[turbo_tasks::function]
45async fn next_edge_free_vars(
46 project_path: FileSystemPath,
47 define_env: Vc<OptionEnvMap>,
48 report_system_env_inlining: Vc<IssueSeverity>,
49) -> Result<Vc<FreeVarReferences>> {
50 Ok(free_var_references!(
51 ..free_var_references_with_vercel_system_env_warnings(
52 defines(&*define_env.await?),
53 *report_system_env_inlining.await?
54 ),
55 Buffer = FreeVarReference::EcmaScriptModule {
56 request: rcstr!("buffer"),
57 lookup_path: Some(project_path),
58 export: Some(rcstr!("Buffer")),
59 },
60 )
61 .cell())
62}
63
64#[turbo_tasks::function]
65pub async fn get_edge_compile_time_info(
66 project_path: FileSystemPath,
67 define_env: Vc<OptionEnvMap>,
68 node_version: ResolvedVc<NodeJsVersion>,
69 report_system_env_inlining: Vc<IssueSeverity>,
70) -> Result<Vc<CompileTimeInfo>> {
71 CompileTimeInfo::builder(
72 Environment::new(ExecutionEnvironment::EdgeWorker(
73 EdgeWorkerEnvironment { node_version }.resolved_cell(),
74 ))
75 .to_resolved()
76 .await?,
77 )
78 .defines(next_edge_defines(define_env).to_resolved().await?)
79 .free_var_references(
80 next_edge_free_vars(project_path, define_env, report_system_env_inlining)
81 .to_resolved()
82 .await?,
83 )
84 .cell()
85 .await
86}
87
88#[turbo_tasks::function]
89pub async fn get_edge_resolve_options_context(
90 project_path: FileSystemPath,
91 ty: ServerContextType,
92 mode: Vc<NextMode>,
93 next_config: Vc<NextConfig>,
94 execution_context: Vc<ExecutionContext>,
95 collected_root_params: Option<Vc<CollectedRootParams>>,
96) -> Result<Vc<ResolveOptionsContext>> {
97 let next_edge_import_map = get_next_edge_import_map(
98 project_path.clone(),
99 ty.clone(),
100 next_config,
101 mode,
102 execution_context,
103 collected_root_params,
104 )
105 .to_resolved()
106 .await?;
107 let next_edge_fallback_import_map =
108 get_next_edge_and_server_fallback_import_map(project_path.clone(), NextRuntime::Edge)
109 .to_resolved()
110 .await?;
111
112 let mut before_resolve_plugins = vec![ResolvedVc::upcast(
113 ModuleFeatureReportResolvePlugin::new(project_path.clone())
114 .to_resolved()
115 .await?,
116 )];
117 if matches!(
118 ty,
119 ServerContextType::Pages { .. }
120 | ServerContextType::AppSSR { .. }
121 | ServerContextType::AppRSC { .. }
122 ) {
123 before_resolve_plugins.push(ResolvedVc::upcast(
124 NextFontLocalResolvePlugin::new(project_path.clone())
125 .to_resolved()
126 .await?,
127 ));
128 };
129
130 let after_resolve_plugins = vec![ResolvedVc::upcast(
131 NextSharedRuntimeResolvePlugin::new(project_path.clone())
132 .to_resolved()
133 .await?,
134 )];
135
136 let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();
138 custom_conditions.extend(NextRuntime::Edge.custom_resolve_conditions());
139
140 if ty.should_use_react_server_condition() {
141 custom_conditions.push(rcstr!("react-server"));
142 };
143
144 let resolve_options_context = ResolveOptionsContext {
149 enable_node_modules: Some(project_path.root().owned().await?),
150 enable_edge_node_externals: true,
151 custom_conditions,
152 import_map: Some(next_edge_import_map),
153 fallback_import_map: Some(next_edge_fallback_import_map),
154 module: true,
155 browser: true,
156 after_resolve_plugins,
157 before_resolve_plugins,
158
159 ..Default::default()
160 };
161
162 let tsconfig_path = next_config.typescript_tsconfig_path().await?;
163 let tsconfig_path = project_path.join(
164 tsconfig_path
165 .as_ref()
166 .unwrap_or(&rcstr!("tsconfig.json")),
169 )?;
170
171 Ok(ResolveOptionsContext {
172 enable_typescript: true,
173 enable_react: true,
174 enable_mjs_extension: true,
175 enable_edge_node_externals: true,
176 custom_extensions: next_config.resolve_extension().owned().await?,
177 tsconfig_path: TsConfigHandling::Fixed(tsconfig_path),
178 rules: vec![(
179 foreign_code_context_condition(next_config, project_path).await?,
180 resolve_options_context.clone().resolved_cell(),
181 )],
182 ..resolve_options_context
183 }
184 .cell())
185}
186
187#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
188pub struct EdgeChunkingContextOptions {
189 pub mode: Vc<NextMode>,
190 pub root_path: FileSystemPath,
191 pub node_root: FileSystemPath,
192 pub output_root_to_root_path: Vc<RcStr>,
193 pub environment: Vc<Environment>,
194 pub module_id_strategy: Vc<ModuleIdStrategy>,
195 pub export_usage: Vc<OptionBindingUsageInfo>,
196 pub unused_references: Vc<UnusedReferences>,
197 pub turbo_minify: Vc<bool>,
198 pub turbo_source_maps: Vc<SourceMapsType>,
199 pub no_mangling: Vc<bool>,
200 pub scope_hoisting: Vc<bool>,
201 pub nested_async_chunking: Vc<bool>,
202 pub client_root: FileSystemPath,
203 pub asset_prefix: RcStr,
204 pub css_url_suffix: Vc<Option<RcStr>>,
205}
206
207#[turbo_tasks::function]
209pub async fn get_edge_chunking_context_with_client_assets(
210 options: EdgeChunkingContextOptions,
211) -> Result<Vc<Box<dyn ChunkingContext>>> {
212 let EdgeChunkingContextOptions {
213 mode,
214 root_path,
215 node_root,
216 output_root_to_root_path,
217 environment,
218 module_id_strategy,
219 export_usage,
220 unused_references,
221 turbo_minify,
222 turbo_source_maps,
223 no_mangling,
224 scope_hoisting,
225 nested_async_chunking,
226 client_root,
227 asset_prefix,
228 css_url_suffix,
229 } = options;
230 let output_root = node_root.join("server/edge")?;
231 let next_mode = mode.await?;
232 let mut builder = BrowserChunkingContext::builder(
233 root_path,
234 output_root.clone(),
235 output_root_to_root_path.owned().await?,
236 client_root.clone(),
237 output_root.join("chunks/ssr")?,
238 client_root.join("static/media")?,
239 environment.to_resolved().await?,
240 next_mode.runtime_type(),
241 )
242 .asset_base_path(Some(asset_prefix))
243 .default_url_behavior(UrlBehavior {
244 suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
245 static_suffix: css_url_suffix.to_resolved().await?,
246 })
247 .minify_type(if *turbo_minify.await? {
248 MinifyType::Minify {
249 mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
251 }
252 } else {
253 MinifyType::NoMinify
254 })
255 .source_maps(*turbo_source_maps.await?)
256 .module_id_strategy(module_id_strategy.to_resolved().await?)
257 .export_usage(*export_usage.await?)
258 .unused_references(unused_references.to_resolved().await?)
259 .nested_async_availability(*nested_async_chunking.await?)
260 .worker_forwarded_globals(worker_forwarded_globals());
261
262 if !next_mode.is_development() {
263 builder = builder
264 .chunking_config(
265 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
266 ChunkingConfig {
267 min_chunk_size: 20_000,
268 ..Default::default()
269 },
270 )
271 .chunking_config(
272 Vc::<CssChunkType>::default().to_resolved().await?,
273 ChunkingConfig {
274 max_merge_chunk_size: 100_000,
275 ..Default::default()
276 },
277 )
278 .module_merging(*scope_hoisting.await?);
279 }
280
281 Ok(Vc::upcast(builder.build()))
282}
283
284#[turbo_tasks::function]
286pub async fn get_edge_chunking_context(
287 options: EdgeChunkingContextOptions,
288) -> Result<Vc<Box<dyn ChunkingContext>>> {
289 let EdgeChunkingContextOptions {
290 mode,
291 root_path,
292 node_root,
293 output_root_to_root_path,
294 environment,
295 module_id_strategy,
296 export_usage,
297 unused_references,
298 turbo_minify,
299 turbo_source_maps,
300 no_mangling,
301 scope_hoisting,
302 nested_async_chunking,
303 client_root,
304 asset_prefix,
305 css_url_suffix,
306 } = options;
307 let css_url_suffix = css_url_suffix.to_resolved().await?;
308 let output_root = node_root.join("server/edge")?;
309 let next_mode = mode.await?;
310 let mut builder = BrowserChunkingContext::builder(
311 root_path,
312 output_root.clone(),
313 output_root_to_root_path.owned().await?,
314 output_root.clone(),
315 output_root.join("chunks")?,
316 output_root.join("assets")?,
317 environment.to_resolved().await?,
318 next_mode.runtime_type(),
319 )
320 .client_roots_override(rcstr!("client"), client_root.clone())
321 .asset_root_path_override(rcstr!("client"), client_root.join("static/media")?)
322 .asset_base_path_override(rcstr!("client"), asset_prefix)
323 .url_behavior_override(
324 rcstr!("client"),
325 UrlBehavior {
326 suffix: AssetSuffix::FromGlobal(rcstr!("NEXT_CLIENT_ASSET_SUFFIX")),
327 static_suffix: css_url_suffix,
328 },
329 )
330 .default_url_behavior(UrlBehavior {
331 suffix: AssetSuffix::Inferred,
332 static_suffix: ResolvedVc::cell(None),
333 })
334 .asset_base_path(Some(rcstr!("blob:server/edge/")))
339 .minify_type(if *turbo_minify.await? {
340 MinifyType::Minify {
341 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
342 }
343 } else {
344 MinifyType::NoMinify
345 })
346 .source_maps(*turbo_source_maps.await?)
347 .module_id_strategy(module_id_strategy.to_resolved().await?)
348 .export_usage(*export_usage.await?)
349 .unused_references(unused_references.to_resolved().await?)
350 .nested_async_availability(*nested_async_chunking.await?)
351 .worker_forwarded_globals(worker_forwarded_globals());
352
353 if !next_mode.is_development() {
354 builder = builder
355 .chunking_config(
356 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
357 ChunkingConfig {
358 min_chunk_size: 20_000,
359 ..Default::default()
360 },
361 )
362 .chunking_config(
363 Vc::<CssChunkType>::default().to_resolved().await?,
364 ChunkingConfig {
365 max_merge_chunk_size: 100_000,
366 ..Default::default()
367 },
368 )
369 .module_merging(*scope_hoisting.await?);
370 }
371
372 Ok(Vc::upcast(builder.build()))
373}