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