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, TypeofWindow,
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::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 custom_conditions = mode.await?.custom_resolve_conditions().collect();
154 let resolve_options_context = ResolveOptionsContext {
155 enable_node_modules: Some(project_path.root().owned().await?),
156 custom_conditions,
157 import_map: Some(next_client_import_map),
158 fallback_import_map: Some(next_client_fallback_import_map),
159 resolved_map: Some(next_client_resolved_map),
160 browser: true,
161 module: true,
162 before_resolve_plugins: vec![
163 ResolvedVc::upcast(
164 get_invalid_server_only_resolve_plugin(project_path.clone())
165 .to_resolved()
166 .await?,
167 ),
168 ResolvedVc::upcast(
169 ModuleFeatureReportResolvePlugin::new(project_path.clone())
170 .to_resolved()
171 .await?,
172 ),
173 ResolvedVc::upcast(
174 NextFontLocalResolvePlugin::new(project_path.clone())
175 .to_resolved()
176 .await?,
177 ),
178 ],
179 after_resolve_plugins: vec![ResolvedVc::upcast(
180 NextSharedRuntimeResolvePlugin::new(project_path.clone())
181 .to_resolved()
182 .await?,
183 )],
184 ..Default::default()
185 };
186
187 Ok(ResolveOptionsContext {
188 enable_typescript: true,
189 enable_react: true,
190 enable_mjs_extension: true,
191 custom_extensions: next_config.resolve_extension().owned().await?,
192 tsconfig_path: next_config
193 .typescript_tsconfig_path()
194 .await?
195 .as_ref()
196 .map(|p| project_path.join(p))
197 .transpose()?,
198 rules: vec![(
199 foreign_code_context_condition(next_config, project_path).await?,
200 resolve_options_context.clone().resolved_cell(),
201 )],
202 ..resolve_options_context
203 }
204 .cell())
205}
206
207#[turbo_tasks::function]
208pub async fn get_client_module_options_context(
209 project_path: FileSystemPath,
210 execution_context: ResolvedVc<ExecutionContext>,
211 env: ResolvedVc<Environment>,
212 ty: ClientContextType,
213 mode: Vc<NextMode>,
214 next_config: Vc<NextConfig>,
215 encryption_key: ResolvedVc<RcStr>,
216) -> Result<Vc<ModuleOptionsContext>> {
217 let next_mode = mode.await?;
218 let resolve_options_context = get_client_resolve_options_context(
219 project_path.clone(),
220 ty.clone(),
221 mode,
222 next_config,
223 *execution_context,
224 );
225
226 let tsconfig = get_typescript_transform_options(project_path.clone())
227 .to_resolved()
228 .await?;
229 let decorators_options = get_decorators_transform_options(project_path.clone());
230 let enable_mdx_rs = *next_config.mdx_rs().await?;
231 let jsx_runtime_options = get_jsx_transform_options(
232 project_path.clone(),
233 mode,
234 Some(resolve_options_context),
235 false,
236 next_config,
237 )
238 .to_resolved()
239 .await?;
240
241 let mut loader_conditions = BTreeSet::new();
242 loader_conditions.insert(WebpackLoaderBuiltinCondition::Browser);
243 loader_conditions.extend(mode.await?.webpack_loader_conditions());
244
245 let mut foreign_conditions = loader_conditions.clone();
249 foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign);
250 let foreign_enable_webpack_loaders =
251 *webpack_loader_options(project_path.clone(), next_config, foreign_conditions).await?;
252
253 let enable_webpack_loaders =
255 *webpack_loader_options(project_path.clone(), next_config, loader_conditions).await?;
256
257 let tree_shaking_mode_for_user_code = *next_config
258 .tree_shaking_mode_for_user_code(next_mode.is_development())
259 .await?;
260 let tree_shaking_mode_for_foreign_code = *next_config
261 .tree_shaking_mode_for_foreign_code(next_mode.is_development())
262 .await?;
263 let target_browsers = env.runtime_versions();
264
265 let mut next_client_rules =
266 get_next_client_transforms_rules(next_config, ty.clone(), mode, false, encryption_key)
267 .await?;
268 let foreign_next_client_rules =
269 get_next_client_transforms_rules(next_config, ty.clone(), mode, true, encryption_key)
270 .await?;
271 let additional_rules: Vec<ModuleRule> = vec![
272 get_swc_ecma_transform_plugin_rule(next_config, project_path.clone()).await?,
273 get_relay_transform_rule(next_config, project_path.clone()).await?,
274 get_emotion_transform_rule(next_config).await?,
275 get_styled_components_transform_rule(next_config).await?,
276 get_styled_jsx_transform_rule(next_config, target_browsers).await?,
277 get_react_remove_properties_transform_rule(next_config).await?,
278 get_remove_console_transform_rule(next_config).await?,
279 ]
280 .into_iter()
281 .flatten()
282 .collect();
283
284 next_client_rules.extend(additional_rules);
285
286 let postcss_transform_options = PostCssTransformOptions {
287 postcss_package: Some(
288 get_postcss_package_mapping(project_path.clone())
289 .to_resolved()
290 .await?,
291 ),
292 config_location: PostCssConfigLocation::ProjectPathOrLocalPath,
293 ..Default::default()
294 };
295 let postcss_foreign_transform_options = PostCssTransformOptions {
296 config_location: PostCssConfigLocation::ProjectPath,
299 ..postcss_transform_options.clone()
300 };
301 let enable_postcss_transform = Some(postcss_transform_options.resolved_cell());
302 let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell());
303
304 let source_maps = if *next_config.client_source_maps(mode).await? {
305 SourceMapsType::Full
306 } else {
307 SourceMapsType::None
308 };
309 let module_options_context = ModuleOptionsContext {
310 ecmascript: EcmascriptOptionsContext {
311 enable_typeof_window_inlining: Some(TypeofWindow::Object),
312 source_maps,
313 ..Default::default()
314 },
315 css: CssOptionsContext {
316 source_maps,
317 module_css_condition: Some(module_styles_rule_condition()),
318 ..Default::default()
319 },
320 environment: Some(env),
321 execution_context: Some(execution_context),
322 tree_shaking_mode: tree_shaking_mode_for_user_code,
323 enable_postcss_transform,
324 side_effect_free_packages: next_config.optimize_package_imports().owned().await?,
325 keep_last_successful_parse: next_mode.is_development(),
326 ..Default::default()
327 };
328
329 let foreign_codes_options_context = ModuleOptionsContext {
331 ecmascript: EcmascriptOptionsContext {
332 enable_typeof_window_inlining: None,
333 ignore_dynamic_requests: true,
335 ..module_options_context.ecmascript
336 },
337 enable_webpack_loaders: foreign_enable_webpack_loaders,
338 enable_postcss_transform: enable_foreign_postcss_transform,
339 module_rules: foreign_next_client_rules,
340 tree_shaking_mode: tree_shaking_mode_for_foreign_code,
341 ..module_options_context.clone()
343 };
344
345 let internal_context = ModuleOptionsContext {
346 ecmascript: EcmascriptOptionsContext {
347 enable_typescript_transform: Some(
348 TypescriptTransformOptions::default().resolved_cell(),
349 ),
350 enable_jsx: Some(JsxTransformOptions::default().resolved_cell()),
351 ..module_options_context.ecmascript.clone()
352 },
353 enable_postcss_transform: None,
354 ..module_options_context.clone()
355 };
356
357 let module_options_context = ModuleOptionsContext {
358 ecmascript: EcmascriptOptionsContext {
362 enable_jsx: Some(jsx_runtime_options),
363 enable_typescript_transform: Some(tsconfig),
364 enable_decorators: Some(decorators_options.to_resolved().await?),
365 ..module_options_context.ecmascript.clone()
366 },
367 enable_webpack_loaders,
368 enable_mdx_rs,
369 rules: vec![
370 (
371 foreign_code_context_condition(next_config, project_path).await?,
372 foreign_codes_options_context.resolved_cell(),
373 ),
374 (
375 internal_assets_conditions().await?,
376 internal_context.resolved_cell(),
377 ),
378 ],
379 module_rules: next_client_rules,
380 ..module_options_context
381 }
382 .cell();
383
384 Ok(module_options_context)
385}
386
387#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Serialize, Deserialize)]
388pub struct ClientChunkingContextOptions {
389 pub mode: Vc<NextMode>,
390 pub root_path: FileSystemPath,
391 pub client_root: FileSystemPath,
392 pub client_root_to_root_path: RcStr,
393 pub asset_prefix: Vc<Option<RcStr>>,
394 pub chunk_suffix_path: Vc<Option<RcStr>>,
395 pub environment: Vc<Environment>,
396 pub module_id_strategy: Vc<Box<dyn ModuleIdStrategy>>,
397 pub export_usage: Vc<OptionExportUsageInfo>,
398 pub minify: Vc<bool>,
399 pub source_maps: Vc<bool>,
400 pub no_mangling: Vc<bool>,
401 pub scope_hoisting: Vc<bool>,
402}
403
404#[turbo_tasks::function]
405pub async fn get_client_chunking_context(
406 options: ClientChunkingContextOptions,
407) -> Result<Vc<Box<dyn ChunkingContext>>> {
408 let ClientChunkingContextOptions {
409 mode,
410 root_path,
411 client_root,
412 client_root_to_root_path,
413 asset_prefix,
414 chunk_suffix_path,
415 environment,
416 module_id_strategy,
417 export_usage,
418 minify,
419 source_maps,
420 no_mangling,
421 scope_hoisting,
422 } = options;
423
424 let next_mode = mode.await?;
425 let asset_prefix = asset_prefix.owned().await?;
426 let chunk_suffix_path = chunk_suffix_path.owned().await?;
427 let mut builder = BrowserChunkingContext::builder(
428 root_path,
429 client_root.clone(),
430 client_root_to_root_path,
431 client_root.clone(),
432 client_root.join("static/chunks")?,
433 get_client_assets_path(client_root.clone()).owned().await?,
434 environment.to_resolved().await?,
435 next_mode.runtime_type(),
436 )
437 .chunk_base_path(asset_prefix.clone())
438 .chunk_suffix_path(chunk_suffix_path)
439 .minify_type(if *minify.await? {
440 MinifyType::Minify {
441 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
442 }
443 } else {
444 MinifyType::NoMinify
445 })
446 .source_maps(if *source_maps.await? {
447 SourceMapsType::Full
448 } else {
449 SourceMapsType::None
450 })
451 .asset_base_path(asset_prefix)
452 .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript)
453 .export_usage(*export_usage.await?)
454 .module_id_strategy(module_id_strategy.to_resolved().await?);
455
456 if next_mode.is_development() {
457 builder = builder
458 .hot_module_replacement()
459 .use_file_source_map_uris()
460 .dynamic_chunk_content_loading(true);
461 } else {
462 builder = builder
463 .chunking_config(
464 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
465 ChunkingConfig {
466 min_chunk_size: 50_000,
467 max_chunk_count_per_group: 40,
468 max_merge_chunk_size: 200_000,
469 ..Default::default()
470 },
471 )
472 .chunking_config(
473 Vc::<CssChunkType>::default().to_resolved().await?,
474 ChunkingConfig {
475 max_merge_chunk_size: 100_000,
476 ..Default::default()
477 },
478 )
479 .use_content_hashing(ContentHashing::Direct { length: 16 })
480 .module_merging(*scope_hoisting.await?);
481 }
482
483 Ok(Vc::upcast(builder.build()))
484}
485
486#[turbo_tasks::function]
487pub fn get_client_assets_path(client_root: FileSystemPath) -> Result<Vc<FileSystemPath>> {
488 Ok(client_root.join("static/media")?.cell())
489}
490
491#[turbo_tasks::function]
492pub async fn get_client_runtime_entries(
493 project_root: FileSystemPath,
494 ty: ClientContextType,
495 mode: Vc<NextMode>,
496 next_config: Vc<NextConfig>,
497 execution_context: Vc<ExecutionContext>,
498) -> Result<Vc<RuntimeEntries>> {
499 let mut runtime_entries = vec![];
500 let resolve_options_context = get_client_resolve_options_context(
501 project_root.clone(),
502 ty.clone(),
503 mode,
504 next_config,
505 execution_context,
506 );
507
508 if mode.await?.is_development() {
509 let enable_react_refresh =
510 assert_can_resolve_react_refresh(project_root.clone(), resolve_options_context)
511 .await?
512 .as_request();
513
514 if let Some(request) = enable_react_refresh {
518 runtime_entries.push(
519 RuntimeEntry::Request(request.to_resolved().await?, project_root.join("_")?)
520 .resolved_cell(),
521 )
522 };
523 }
524
525 if matches!(ty, ClientContextType::App { .. },) {
526 runtime_entries.push(
527 RuntimeEntry::Request(
528 Request::parse(Pattern::Constant(rcstr!(
529 "next/dist/client/app-next-turbopack.js"
530 )))
531 .to_resolved()
532 .await?,
533 project_root.join("_")?,
534 )
535 .resolved_cell(),
536 );
537 }
538
539 Ok(Vc::cell(runtime_entries))
540}