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