1use std::collections::BTreeSet;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use turbo_rcstr::{RcStr, rcstr};
6use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
7use turbo_tasks_fs::FileSystemPath;
8use turbopack::module_options::{
9 CssOptionsContext, EcmascriptOptionsContext, JsxTransformOptions, ModuleRule,
10 TypescriptTransformOptions, module_options_context::ModuleOptionsContext,
11 side_effect_free_packages_glob,
12};
13use turbopack_browser::{
14 BrowserChunkingContext, ChunkSuffix, ContentHashing, CurrentChunkMethod,
15 react_refresh::assert_can_resolve_react_refresh,
16};
17use turbopack_core::{
18 chunk::{
19 ChunkingConfig, ChunkingContext, MangleType, MinifyType, SourceMapSourceType,
20 SourceMapsType, module_id_strategies::ModuleIdStrategy,
21 },
22 compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
23 environment::{BrowserEnvironment, Environment, ExecutionEnvironment},
24 free_var_references,
25 module_graph::binding_usage_info::OptionBindingUsageInfo,
26 resolve::{parse::Request, pattern::Pattern},
27};
28use turbopack_css::chunk::CssChunkType;
29use turbopack_ecmascript::{AnalyzeMode, TypeofWindow, chunk::EcmascriptChunkType};
30use turbopack_node::{
31 execution_context::ExecutionContext,
32 transforms::postcss::{PostCssConfigLocation, PostCssTransformOptions},
33};
34use turbopack_resolve::resolve_options_context::ResolveOptionsContext;
35
36use crate::{
37 mode::NextMode,
38 next_build::get_postcss_package_mapping,
39 next_client::{
40 runtime_entry::{RuntimeEntries, RuntimeEntry},
41 transforms::get_next_client_transforms_rules,
42 },
43 next_config::NextConfig,
44 next_font::local::NextFontLocalResolvePlugin,
45 next_import_map::{
46 get_next_client_fallback_import_map, get_next_client_import_map,
47 get_next_client_resolved_map,
48 },
49 next_shared::{
50 resolve::{
51 ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin,
52 get_invalid_server_only_resolve_plugin,
53 },
54 transforms::{
55 emotion::get_emotion_transform_rule,
56 react_remove_properties::get_react_remove_properties_transform_rule,
57 relay::get_relay_transform_rule, remove_console::get_remove_console_transform_rule,
58 styled_components::get_styled_components_transform_rule,
59 styled_jsx::get_styled_jsx_transform_rule,
60 swc_ecma_transform_plugins::get_swc_ecma_transform_plugin_rule,
61 },
62 webpack_rules::{WebpackLoaderBuiltinCondition, webpack_loader_options},
63 },
64 transform_options::{
65 get_decorators_transform_options, get_jsx_transform_options,
66 get_typescript_transform_options,
67 },
68 util::{
69 OptionEnvMap, defines, foreign_code_context_condition, internal_assets_conditions,
70 module_styles_rule_condition,
71 },
72};
73
74#[turbo_tasks::function]
75async fn next_client_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
76 Ok(defines(&*define_env.await?).cell())
77}
78
79#[turbo_tasks::function]
80async fn next_client_free_vars(define_env: Vc<OptionEnvMap>) -> Result<Vc<FreeVarReferences>> {
81 Ok(free_var_references!(
82 ..defines(&*define_env.await?).into_iter(),
83 Buffer = FreeVarReference::EcmaScriptModule {
84 request: rcstr!("node:buffer"),
85 lookup_path: None,
86 export: Some(rcstr!("Buffer")),
87 },
88 process = FreeVarReference::EcmaScriptModule {
89 request: rcstr!("node:process"),
90 lookup_path: None,
91 export: Some(rcstr!("default")),
92 }
93 )
94 .cell())
95}
96
97#[turbo_tasks::function]
98pub async fn get_client_compile_time_info(
99 browserslist_query: RcStr,
100 define_env: Vc<OptionEnvMap>,
101) -> Result<Vc<CompileTimeInfo>> {
102 CompileTimeInfo::builder(
103 Environment::new(ExecutionEnvironment::Browser(
104 BrowserEnvironment {
105 dom: true,
106 web_worker: false,
107 service_worker: false,
108 browserslist_query: browserslist_query.to_owned(),
109 }
110 .resolved_cell(),
111 ))
112 .to_resolved()
113 .await?,
114 )
115 .defines(next_client_defines(define_env).to_resolved().await?)
116 .free_var_references(next_client_free_vars(define_env).to_resolved().await?)
117 .cell()
118 .await
119}
120
121#[turbo_tasks::value(shared)]
122#[derive(Debug, Clone, Hash, TaskInput)]
123pub enum ClientContextType {
124 Pages { pages_dir: FileSystemPath },
125 App { app_dir: FileSystemPath },
126 Fallback,
127 Other,
128}
129
130#[turbo_tasks::function]
131pub async fn get_client_resolve_options_context(
132 project_path: FileSystemPath,
133 ty: ClientContextType,
134 mode: Vc<NextMode>,
135 next_config: Vc<NextConfig>,
136 execution_context: Vc<ExecutionContext>,
137) -> Result<Vc<ResolveOptionsContext>> {
138 let next_client_import_map = get_next_client_import_map(
139 project_path.clone(),
140 ty.clone(),
141 next_config,
142 mode,
143 execution_context,
144 )
145 .to_resolved()
146 .await?;
147 let next_client_fallback_import_map = get_next_client_fallback_import_map(ty.clone())
148 .to_resolved()
149 .await?;
150 let next_client_resolved_map =
151 get_next_client_resolved_map(project_path.clone(), project_path.clone(), *mode.await?)
152 .to_resolved()
153 .await?;
154 let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();
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 custom_conditions,
163 import_map: Some(next_client_import_map),
164 fallback_import_map: Some(next_client_fallback_import_map),
165 resolved_map: Some(next_client_resolved_map),
166 browser: true,
167 module: true,
168 before_resolve_plugins: vec![
169 ResolvedVc::upcast(
170 get_invalid_server_only_resolve_plugin(project_path.clone())
171 .to_resolved()
172 .await?,
173 ),
174 ResolvedVc::upcast(
175 ModuleFeatureReportResolvePlugin::new(project_path.clone())
176 .to_resolved()
177 .await?,
178 ),
179 ResolvedVc::upcast(
180 NextFontLocalResolvePlugin::new(project_path.clone())
181 .to_resolved()
182 .await?,
183 ),
184 ],
185 after_resolve_plugins: vec![ResolvedVc::upcast(
186 NextSharedRuntimeResolvePlugin::new(project_path.clone())
187 .to_resolved()
188 .await?,
189 )],
190 ..Default::default()
191 };
192
193 let tsconfig_path = next_config
194 .typescript_tsconfig_path()
195 .await?
196 .as_ref()
197 .or(Some(&RcStr::from("tsconfig.json")))
200 .map(|p| project_path.join(p))
201 .transpose()?;
202
203 Ok(ResolveOptionsContext {
204 enable_typescript: true,
205 enable_react: true,
206 enable_mjs_extension: true,
207 custom_extensions: next_config.resolve_extension().owned().await?,
208 tsconfig_path,
209 rules: vec![(
210 foreign_code_context_condition(next_config, project_path).await?,
211 resolve_options_context.clone().resolved_cell(),
212 )],
213 ..resolve_options_context
214 }
215 .cell())
216}
217
218#[turbo_tasks::function]
219pub async fn get_client_module_options_context(
220 project_path: FileSystemPath,
221 execution_context: ResolvedVc<ExecutionContext>,
222 env: ResolvedVc<Environment>,
223 ty: ClientContextType,
224 mode: Vc<NextMode>,
225 next_config: Vc<NextConfig>,
226 encryption_key: ResolvedVc<RcStr>,
227) -> Result<Vc<ModuleOptionsContext>> {
228 let next_mode = mode.await?;
229 let resolve_options_context = get_client_resolve_options_context(
230 project_path.clone(),
231 ty.clone(),
232 mode,
233 next_config,
234 *execution_context,
235 );
236
237 let tsconfig_path = next_config
238 .typescript_tsconfig_path()
239 .await?
240 .as_ref()
241 .map(|p| project_path.join(p))
242 .transpose()?;
243
244 let tsconfig = get_typescript_transform_options(project_path.clone(), tsconfig_path.clone())
245 .to_resolved()
246 .await?;
247 let decorators_options =
248 get_decorators_transform_options(project_path.clone(), tsconfig_path.clone());
249 let enable_mdx_rs = *next_config.mdx_rs().await?;
250 let jsx_runtime_options = get_jsx_transform_options(
251 project_path.clone(),
252 mode,
253 Some(resolve_options_context),
254 false,
255 next_config,
256 tsconfig_path,
257 )
258 .to_resolved()
259 .await?;
260
261 let mut loader_conditions = BTreeSet::new();
262 loader_conditions.insert(WebpackLoaderBuiltinCondition::Browser);
263 loader_conditions.extend(mode.await?.webpack_loader_conditions());
264
265 let mut foreign_conditions = loader_conditions.clone();
269 foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign);
270 let foreign_enable_webpack_loaders =
271 *webpack_loader_options(project_path.clone(), next_config, foreign_conditions).await?;
272
273 let enable_webpack_loaders =
275 *webpack_loader_options(project_path.clone(), next_config, loader_conditions).await?;
276
277 let tree_shaking_mode_for_user_code = *next_config
278 .tree_shaking_mode_for_user_code(next_mode.is_development())
279 .await?;
280 let tree_shaking_mode_for_foreign_code = *next_config
281 .tree_shaking_mode_for_foreign_code(next_mode.is_development())
282 .await?;
283 let target_browsers = env.runtime_versions();
284
285 let mut next_client_rules =
286 get_next_client_transforms_rules(next_config, ty.clone(), mode, false, encryption_key)
287 .await?;
288 let foreign_next_client_rules =
289 get_next_client_transforms_rules(next_config, ty.clone(), mode, true, encryption_key)
290 .await?;
291 let additional_rules: Vec<ModuleRule> = vec![
292 get_swc_ecma_transform_plugin_rule(next_config, project_path.clone()).await?,
293 get_relay_transform_rule(next_config, project_path.clone()).await?,
294 get_emotion_transform_rule(next_config).await?,
295 get_styled_components_transform_rule(next_config).await?,
296 get_styled_jsx_transform_rule(next_config, target_browsers).await?,
297 get_react_remove_properties_transform_rule(next_config).await?,
298 get_remove_console_transform_rule(next_config).await?,
299 ]
300 .into_iter()
301 .flatten()
302 .collect();
303
304 next_client_rules.extend(additional_rules);
305
306 let postcss_transform_options = PostCssTransformOptions {
307 postcss_package: Some(
308 get_postcss_package_mapping(project_path.clone())
309 .to_resolved()
310 .await?,
311 ),
312 config_location: PostCssConfigLocation::ProjectPathOrLocalPath,
313 ..Default::default()
314 };
315 let postcss_foreign_transform_options = PostCssTransformOptions {
316 config_location: PostCssConfigLocation::ProjectPath,
319 ..postcss_transform_options.clone()
320 };
321 let enable_postcss_transform = Some(postcss_transform_options.resolved_cell());
322 let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell());
323
324 let source_maps = *next_config.client_source_maps(mode).await?;
325 let module_options_context = ModuleOptionsContext {
326 ecmascript: EcmascriptOptionsContext {
327 enable_typeof_window_inlining: Some(TypeofWindow::Object),
328 source_maps,
329 infer_module_side_effects: *next_config.turbopack_infer_module_side_effects().await?,
330 ..Default::default()
331 },
332 css: CssOptionsContext {
333 source_maps,
334 module_css_condition: Some(module_styles_rule_condition()),
335 ..Default::default()
336 },
337 environment: Some(env),
338 execution_context: Some(execution_context),
339 tree_shaking_mode: tree_shaking_mode_for_user_code,
340 enable_postcss_transform,
341 side_effect_free_packages: Some(
342 side_effect_free_packages_glob(next_config.optimize_package_imports())
343 .to_resolved()
344 .await?,
345 ),
346 keep_last_successful_parse: next_mode.is_development(),
347 analyze_mode: if next_mode.is_development() {
348 AnalyzeMode::CodeGeneration
349 } else {
350 AnalyzeMode::CodeGenerationAndTracing
354 },
355 ..Default::default()
356 };
357
358 let foreign_codes_options_context = ModuleOptionsContext {
360 ecmascript: EcmascriptOptionsContext {
361 enable_typeof_window_inlining: None,
362 ignore_dynamic_requests: true,
364 ..module_options_context.ecmascript
365 },
366 enable_webpack_loaders: foreign_enable_webpack_loaders,
367 enable_postcss_transform: enable_foreign_postcss_transform,
368 module_rules: foreign_next_client_rules,
369 tree_shaking_mode: tree_shaking_mode_for_foreign_code,
370 ..module_options_context.clone()
372 };
373
374 let internal_context = ModuleOptionsContext {
375 ecmascript: EcmascriptOptionsContext {
376 enable_typescript_transform: Some(
377 TypescriptTransformOptions::default().resolved_cell(),
378 ),
379 enable_jsx: Some(JsxTransformOptions::default().resolved_cell()),
380 ..module_options_context.ecmascript.clone()
381 },
382 enable_postcss_transform: None,
383 ..module_options_context.clone()
384 };
385
386 let module_options_context = ModuleOptionsContext {
387 ecmascript: EcmascriptOptionsContext {
391 enable_jsx: Some(jsx_runtime_options),
392 enable_typescript_transform: Some(tsconfig),
393 enable_decorators: Some(decorators_options.to_resolved().await?),
394 ..module_options_context.ecmascript.clone()
395 },
396 enable_webpack_loaders,
397 enable_mdx_rs,
398 rules: vec![
399 (
400 foreign_code_context_condition(next_config, project_path).await?,
401 foreign_codes_options_context.resolved_cell(),
402 ),
403 (
404 internal_assets_conditions().await?,
405 internal_context.resolved_cell(),
406 ),
407 ],
408 module_rules: next_client_rules,
409 ..module_options_context
410 }
411 .cell();
412
413 Ok(module_options_context)
414}
415
416#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
417pub struct ClientChunkingContextOptions {
418 pub mode: Vc<NextMode>,
419 pub root_path: FileSystemPath,
420 pub client_root: FileSystemPath,
421 pub client_root_to_root_path: RcStr,
422 pub asset_prefix: Vc<RcStr>,
423 pub environment: Vc<Environment>,
424 pub module_id_strategy: Vc<Box<dyn ModuleIdStrategy>>,
425 pub export_usage: Vc<OptionBindingUsageInfo>,
426 pub unused_references: Vc<OptionBindingUsageInfo>,
427 pub minify: Vc<bool>,
428 pub source_maps: Vc<SourceMapsType>,
429 pub no_mangling: Vc<bool>,
430 pub scope_hoisting: Vc<bool>,
431 pub nested_async_chunking: Vc<bool>,
432 pub debug_ids: Vc<bool>,
433 pub should_use_absolute_url_references: Vc<bool>,
434}
435
436#[turbo_tasks::function]
437pub async fn get_client_chunking_context(
438 options: ClientChunkingContextOptions,
439) -> Result<Vc<Box<dyn ChunkingContext>>> {
440 let ClientChunkingContextOptions {
441 mode,
442 root_path,
443 client_root,
444 client_root_to_root_path,
445 asset_prefix,
446 environment,
447 module_id_strategy,
448 export_usage,
449 unused_references,
450 minify,
451 source_maps,
452 no_mangling,
453 scope_hoisting,
454 nested_async_chunking,
455 debug_ids,
456 should_use_absolute_url_references,
457 } = options;
458
459 let next_mode = mode.await?;
460 let asset_prefix = asset_prefix.owned().await?;
461 let mut builder = BrowserChunkingContext::builder(
462 root_path,
463 client_root.clone(),
464 client_root_to_root_path,
465 client_root.clone(),
466 client_root.join("static/chunks")?,
467 get_client_assets_path(client_root.clone()).owned().await?,
468 environment.to_resolved().await?,
469 next_mode.runtime_type(),
470 )
471 .chunk_base_path(Some(asset_prefix.clone()))
472 .chunk_suffix(ChunkSuffix::FromScriptSrc.resolved_cell())
473 .minify_type(if *minify.await? {
474 MinifyType::Minify {
475 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
476 }
477 } else {
478 MinifyType::NoMinify
479 })
480 .source_maps(*source_maps.await?)
481 .asset_base_path(Some(asset_prefix))
482 .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript)
483 .export_usage(*export_usage.await?)
484 .unused_references(*unused_references.await?)
485 .module_id_strategy(module_id_strategy.to_resolved().await?)
486 .debug_ids(*debug_ids.await?)
487 .should_use_absolute_url_references(*should_use_absolute_url_references.await?)
488 .nested_async_availability(*nested_async_chunking.await?);
489
490 if next_mode.is_development() {
491 builder = builder
492 .hot_module_replacement()
493 .source_map_source_type(SourceMapSourceType::AbsoluteFileUri)
494 .dynamic_chunk_content_loading(true);
495 } else {
496 builder = builder
497 .chunking_config(
498 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
499 ChunkingConfig {
500 min_chunk_size: 50_000,
501 max_chunk_count_per_group: 40,
502 max_merge_chunk_size: 200_000,
503 ..Default::default()
504 },
505 )
506 .chunking_config(
507 Vc::<CssChunkType>::default().to_resolved().await?,
508 ChunkingConfig {
509 max_merge_chunk_size: 100_000,
510 ..Default::default()
511 },
512 )
513 .use_content_hashing(ContentHashing::Direct { length: 16 })
514 .module_merging(*scope_hoisting.await?);
515 }
516
517 Ok(Vc::upcast(builder.build()))
518}
519
520#[turbo_tasks::function]
521pub fn get_client_assets_path(client_root: FileSystemPath) -> Result<Vc<FileSystemPath>> {
522 Ok(client_root.join("static/media")?.cell())
523}
524
525#[turbo_tasks::function]
526pub async fn get_client_runtime_entries(
527 project_root: FileSystemPath,
528 ty: ClientContextType,
529 mode: Vc<NextMode>,
530 next_config: Vc<NextConfig>,
531 execution_context: Vc<ExecutionContext>,
532) -> Result<Vc<RuntimeEntries>> {
533 let mut runtime_entries = vec![];
534 let resolve_options_context = get_client_resolve_options_context(
535 project_root.clone(),
536 ty.clone(),
537 mode,
538 next_config,
539 execution_context,
540 );
541
542 if mode.await?.is_development() {
543 let enable_react_refresh =
544 assert_can_resolve_react_refresh(project_root.clone(), resolve_options_context)
545 .await?
546 .as_request();
547
548 if let Some(request) = enable_react_refresh {
552 runtime_entries.push(
553 RuntimeEntry::Request(request.to_resolved().await?, project_root.join("_")?)
554 .resolved_cell(),
555 )
556 };
557 }
558
559 if matches!(ty, ClientContextType::App { .. },) {
560 runtime_entries.push(
561 RuntimeEntry::Request(
562 Request::parse(Pattern::Constant(rcstr!(
563 "next/dist/client/app-next-turbopack.js"
564 )))
565 .to_resolved()
566 .await?,
567 project_root.join("_")?,
568 )
569 .resolved_cell(),
570 );
571 }
572
573 Ok(Vc::cell(runtime_entries))
574}