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