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