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