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::Middleware { .. }
128 | ServerContextType::Instrumentation { .. }
129 ) {
130 before_resolve_plugins.push(ResolvedVc::upcast(
131 get_invalid_client_only_resolve_plugin(project_path.clone())
132 .to_resolved()
133 .await?,
134 ));
135 before_resolve_plugins.push(ResolvedVc::upcast(
136 get_invalid_styled_jsx_resolve_plugin(project_path.clone())
137 .to_resolved()
138 .await?,
139 ));
140 }
141
142 let after_resolve_plugins = vec![ResolvedVc::upcast(
143 NextSharedRuntimeResolvePlugin::new(project_path.clone())
144 .to_resolved()
145 .await?,
146 )];
147
148 let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();
150 custom_conditions.extend(NextRuntime::Edge.custom_resolve_conditions());
151
152 if ty.should_use_react_server_condition() {
153 custom_conditions.push(rcstr!("react-server"));
154 };
155
156 if *next_config.enable_cache_components().await? {
157 custom_conditions.push(rcstr!("next-js"));
158 };
159
160 let resolve_options_context = ResolveOptionsContext {
161 enable_node_modules: Some(project_path.root().owned().await?),
162 enable_edge_node_externals: true,
163 custom_conditions,
164 import_map: Some(next_edge_import_map),
165 fallback_import_map: Some(next_edge_fallback_import_map),
166 module: true,
167 browser: true,
168 after_resolve_plugins,
169 before_resolve_plugins,
170
171 ..Default::default()
172 };
173
174 Ok(ResolveOptionsContext {
175 enable_typescript: true,
176 enable_react: true,
177 enable_mjs_extension: true,
178 enable_edge_node_externals: true,
179 custom_extensions: next_config.resolve_extension().owned().await?,
180 tsconfig_path: next_config
181 .typescript_tsconfig_path()
182 .await?
183 .as_ref()
184 .or(Some(&RcStr::from("tsconfig.json")))
187 .map(|p| project_path.join(p))
188 .transpose()?,
189 rules: vec![(
190 foreign_code_context_condition(next_config, project_path).await?,
191 resolve_options_context.clone().resolved_cell(),
192 )],
193 ..resolve_options_context
194 }
195 .cell())
196}
197
198#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
199pub struct EdgeChunkingContextOptions {
200 pub mode: Vc<NextMode>,
201 pub root_path: FileSystemPath,
202 pub node_root: FileSystemPath,
203 pub output_root_to_root_path: Vc<RcStr>,
204 pub environment: Vc<Environment>,
205 pub module_id_strategy: Vc<Box<dyn ModuleIdStrategy>>,
206 pub export_usage: Vc<OptionExportUsageInfo>,
207 pub turbo_minify: Vc<bool>,
208 pub turbo_source_maps: Vc<bool>,
209 pub no_mangling: Vc<bool>,
210 pub scope_hoisting: Vc<bool>,
211 pub client_root: FileSystemPath,
212 pub asset_prefix: RcStr,
213}
214
215#[turbo_tasks::function]
217pub async fn get_edge_chunking_context_with_client_assets(
218 options: EdgeChunkingContextOptions,
219) -> Result<Vc<Box<dyn ChunkingContext>>> {
220 let EdgeChunkingContextOptions {
221 mode,
222 root_path,
223 node_root,
224 output_root_to_root_path,
225 environment,
226 module_id_strategy,
227 export_usage,
228 turbo_minify,
229 turbo_source_maps,
230 no_mangling,
231 scope_hoisting,
232 client_root,
233 asset_prefix,
234 } = options;
235 let output_root = node_root.join("server/edge")?;
236 let next_mode = mode.await?;
237 let mut builder = BrowserChunkingContext::builder(
238 root_path,
239 output_root.clone(),
240 output_root_to_root_path.owned().await?,
241 client_root.clone(),
242 output_root.join("chunks/ssr")?,
243 client_root.join("static/media")?,
244 environment.to_resolved().await?,
245 next_mode.runtime_type(),
246 )
247 .asset_base_path(Some(asset_prefix))
248 .minify_type(if *turbo_minify.await? {
249 MinifyType::Minify {
250 mangle: (!*no_mangling.await?).then_some(MangleType::Deterministic),
252 }
253 } else {
254 MinifyType::NoMinify
255 })
256 .source_maps(if *turbo_source_maps.await? {
257 SourceMapsType::Full
258 } else {
259 SourceMapsType::None
260 })
261 .module_id_strategy(module_id_strategy.to_resolved().await?)
262 .export_usage(*export_usage.await?);
263
264 if !next_mode.is_development() {
265 builder = builder
266 .chunking_config(
267 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
268 ChunkingConfig {
269 min_chunk_size: 20_000,
270 ..Default::default()
271 },
272 )
273 .chunking_config(
274 Vc::<CssChunkType>::default().to_resolved().await?,
275 ChunkingConfig {
276 max_merge_chunk_size: 100_000,
277 ..Default::default()
278 },
279 )
280 .module_merging(*scope_hoisting.await?);
281 }
282
283 Ok(Vc::upcast(builder.build()))
284}
285
286#[turbo_tasks::function]
288pub async fn get_edge_chunking_context(
289 options: EdgeChunkingContextOptions,
290) -> Result<Vc<Box<dyn ChunkingContext>>> {
291 let EdgeChunkingContextOptions {
292 mode,
293 root_path,
294 node_root,
295 output_root_to_root_path,
296 environment,
297 module_id_strategy,
298 export_usage,
299 turbo_minify,
300 turbo_source_maps,
301 no_mangling,
302 scope_hoisting,
303 client_root,
304 asset_prefix,
305 } = options;
306 let output_root = node_root.join("server/edge")?;
307 let next_mode = mode.await?;
308 let mut builder = BrowserChunkingContext::builder(
309 root_path,
310 output_root.clone(),
311 output_root_to_root_path.owned().await?,
312 output_root.clone(),
313 output_root.join("chunks")?,
314 output_root.join("assets")?,
315 environment.to_resolved().await?,
316 next_mode.runtime_type(),
317 )
318 .client_roots_override(rcstr!("client"), client_root.clone())
319 .asset_root_path_override(rcstr!("client"), client_root.join("static/media")?)
320 .asset_base_path_override(rcstr!("client"), asset_prefix)
321 .asset_base_path(Some(rcstr!("blob:server/edge/")))
326 .minify_type(if *turbo_minify.await? {
327 MinifyType::Minify {
328 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
329 }
330 } else {
331 MinifyType::NoMinify
332 })
333 .source_maps(if *turbo_source_maps.await? {
334 SourceMapsType::Full
335 } else {
336 SourceMapsType::None
337 })
338 .module_id_strategy(module_id_strategy.to_resolved().await?)
339 .export_usage(*export_usage.await?);
340
341 if !next_mode.is_development() {
342 builder = builder
343 .chunking_config(
344 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
345 ChunkingConfig {
346 min_chunk_size: 20_000,
347 ..Default::default()
348 },
349 )
350 .chunking_config(
351 Vc::<CssChunkType>::default().to_resolved().await?,
352 ChunkingConfig {
353 max_merge_chunk_size: 100_000,
354 ..Default::default()
355 },
356 )
357 .module_merging(*scope_hoisting.await?);
358 }
359
360 Ok(Vc::upcast(builder.build()))
361}