1use std::iter::once;
2
3use anyhow::Result;
4use turbo_rcstr::RcStr;
5use turbo_tasks::{FxIndexMap, OptionVcExt, ResolvedVc, Value, Vc};
6use turbo_tasks_env::EnvMap;
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::{
26 CompileTimeDefineValue, CompileTimeDefines, CompileTimeInfo, DefineableNameSegment,
27 FreeVarReference, FreeVarReferences,
28 },
29 environment::{BrowserEnvironment, Environment, ExecutionEnvironment},
30 free_var_references,
31 resolve::{parse::Request, pattern::Pattern},
32};
33use turbopack_ecmascript::chunk::EcmascriptChunkType;
34use turbopack_node::{
35 execution_context::ExecutionContext,
36 transforms::postcss::{PostCssConfigLocation, PostCssTransformOptions},
37};
38
39use super::transforms::get_next_client_transforms_rules;
40use crate::{
41 mode::NextMode,
42 next_build::get_postcss_package_mapping,
43 next_client::runtime_entry::{RuntimeEntries, RuntimeEntry},
44 next_config::NextConfig,
45 next_font::local::NextFontLocalResolvePlugin,
46 next_import_map::{
47 get_next_client_fallback_import_map, get_next_client_import_map,
48 get_next_client_resolved_map,
49 },
50 next_shared::{
51 resolve::{
52 ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin,
53 get_invalid_server_only_resolve_plugin,
54 },
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::webpack_loader_options,
64 },
65 transform_options::{
66 get_decorators_transform_options, get_jsx_transform_options,
67 get_typescript_transform_options,
68 },
69 util::{foreign_code_context_condition, internal_assets_conditions},
70};
71
72fn defines(define_env: &FxIndexMap<RcStr, RcStr>) -> CompileTimeDefines {
73 let mut defines = FxIndexMap::default();
74
75 for (k, v) in define_env {
76 defines
77 .entry(
78 k.split('.')
79 .map(|s| DefineableNameSegment::Name(s.into()))
80 .collect::<Vec<_>>(),
81 )
82 .or_insert_with(|| {
83 let val = serde_json::from_str(v);
84 match val {
85 Ok(serde_json::Value::Bool(v)) => CompileTimeDefineValue::Bool(v),
86 Ok(serde_json::Value::String(v)) => CompileTimeDefineValue::String(v.into()),
87 _ => CompileTimeDefineValue::JSON(v.clone()),
88 }
89 });
90 }
91
92 CompileTimeDefines(defines)
93}
94
95#[turbo_tasks::function]
96async fn next_client_defines(define_env: Vc<EnvMap>) -> Result<Vc<CompileTimeDefines>> {
97 Ok(defines(&*define_env.await?).cell())
98}
99
100#[turbo_tasks::function]
101async fn next_client_free_vars(define_env: Vc<EnvMap>) -> Result<Vc<FreeVarReferences>> {
102 Ok(free_var_references!(
103 ..defines(&*define_env.await?).into_iter(),
104 Buffer = FreeVarReference::EcmaScriptModule {
105 request: "node:buffer".into(),
106 lookup_path: None,
107 export: Some("Buffer".into()),
108 },
109 process = FreeVarReference::EcmaScriptModule {
110 request: "node:process".into(),
111 lookup_path: None,
112 export: Some("default".into()),
113 }
114 )
115 .cell())
116}
117
118#[turbo_tasks::function]
119pub async fn get_client_compile_time_info(
120 browserslist_query: RcStr,
121 define_env: Vc<EnvMap>,
122) -> Result<Vc<CompileTimeInfo>> {
123 CompileTimeInfo::builder(
124 Environment::new(Value::new(ExecutionEnvironment::Browser(
125 BrowserEnvironment {
126 dom: true,
127 web_worker: false,
128 service_worker: false,
129 browserslist_query: browserslist_query.to_owned(),
130 }
131 .resolved_cell(),
132 )))
133 .to_resolved()
134 .await?,
135 )
136 .defines(next_client_defines(define_env).to_resolved().await?)
137 .free_var_references(next_client_free_vars(define_env).to_resolved().await?)
138 .cell()
139 .await
140}
141
142#[turbo_tasks::value(shared, serialization = "auto_for_input")]
143#[derive(Debug, Copy, Clone, Hash)]
144pub enum ClientContextType {
145 Pages {
146 pages_dir: ResolvedVc<FileSystemPath>,
147 },
148 App {
149 app_dir: ResolvedVc<FileSystemPath>,
150 },
151 Fallback,
152 Other,
153}
154
155#[turbo_tasks::function]
156pub async fn get_client_resolve_options_context(
157 project_path: ResolvedVc<FileSystemPath>,
158 ty: Value<ClientContextType>,
159 mode: Vc<NextMode>,
160 next_config: Vc<NextConfig>,
161 execution_context: Vc<ExecutionContext>,
162) -> Result<Vc<ResolveOptionsContext>> {
163 let next_client_import_map =
164 get_next_client_import_map(*project_path, ty, next_config, execution_context)
165 .to_resolved()
166 .await?;
167 let next_client_fallback_import_map = get_next_client_fallback_import_map(ty)
168 .to_resolved()
169 .await?;
170 let next_client_resolved_map =
171 get_next_client_resolved_map(*project_path, project_path, *mode.await?)
172 .to_resolved()
173 .await?;
174 let custom_conditions = vec![mode.await?.condition().into()];
175 let resolve_options_context = ResolveOptionsContext {
176 enable_node_modules: Some(project_path.root().to_resolved().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 get_invalid_server_only_resolve_plugin(project_path)
186 .to_resolved()
187 .await?,
188 ),
189 ResolvedVc::upcast(
190 ModuleFeatureReportResolvePlugin::new(*project_path)
191 .to_resolved()
192 .await?,
193 ),
194 ResolvedVc::upcast(
195 NextFontLocalResolvePlugin::new(*project_path)
196 .to_resolved()
197 .await?,
198 ),
199 ],
200 after_resolve_plugins: vec![ResolvedVc::upcast(
201 NextSharedRuntimeResolvePlugin::new(*project_path)
202 .to_resolved()
203 .await?,
204 )],
205 ..Default::default()
206 };
207
208 Ok(ResolveOptionsContext {
209 enable_typescript: true,
210 enable_react: true,
211 enable_mjs_extension: true,
212 custom_extensions: next_config.resolve_extension().owned().await?,
213 tsconfig_path: next_config
214 .typescript_tsconfig_path()
215 .await?
216 .as_ref()
217 .map(|p| project_path.join(p.to_owned()))
218 .to_resolved()
219 .await?,
220 rules: vec![(
221 foreign_code_context_condition(next_config, project_path).await?,
222 resolve_options_context.clone().resolved_cell(),
223 )],
224 ..resolve_options_context
225 }
226 .cell())
227}
228
229#[turbo_tasks::function]
230pub async fn get_client_module_options_context(
231 project_path: ResolvedVc<FileSystemPath>,
232 execution_context: ResolvedVc<ExecutionContext>,
233 env: ResolvedVc<Environment>,
234 ty: Value<ClientContextType>,
235 mode: Vc<NextMode>,
236 next_config: Vc<NextConfig>,
237 encryption_key: ResolvedVc<RcStr>,
238 no_mangling: Vc<bool>,
239) -> Result<Vc<ModuleOptionsContext>> {
240 let next_mode = mode.await?;
241 let resolve_options_context = get_client_resolve_options_context(
242 *project_path,
243 ty,
244 mode,
245 next_config,
246 *execution_context,
247 );
248
249 let tsconfig = get_typescript_transform_options(*project_path)
250 .to_resolved()
251 .await?;
252 let decorators_options = get_decorators_transform_options(*project_path);
253 let enable_mdx_rs = *next_config.mdx_rs().await?;
254 let jsx_runtime_options = get_jsx_transform_options(
255 *project_path,
256 mode,
257 Some(resolve_options_context),
258 false,
259 next_config,
260 )
261 .to_resolved()
262 .await?;
263
264 let conditions = vec!["browser".into(), mode.await?.condition().into()];
269 let foreign_enable_webpack_loaders = webpack_loader_options(
270 project_path,
271 next_config,
272 true,
273 conditions
274 .iter()
275 .cloned()
276 .chain(once("foreign".into()))
277 .collect(),
278 )
279 .await?;
280
281 let enable_webpack_loaders =
283 webpack_loader_options(project_path, next_config, false, conditions).await?;
284
285 let tree_shaking_mode_for_user_code = *next_config
286 .tree_shaking_mode_for_user_code(next_mode.is_development())
287 .await?;
288 let tree_shaking_mode_for_foreign_code = *next_config
289 .tree_shaking_mode_for_foreign_code(next_mode.is_development())
290 .await?;
291 let target_browsers = env.runtime_versions();
292
293 let mut next_client_rules =
294 get_next_client_transforms_rules(next_config, ty.into_value(), mode, false, encryption_key)
295 .await?;
296 let foreign_next_client_rules =
297 get_next_client_transforms_rules(next_config, ty.into_value(), mode, true, encryption_key)
298 .await?;
299 let additional_rules: Vec<ModuleRule> = vec![
300 get_swc_ecma_transform_plugin_rule(next_config, project_path).await?,
301 get_relay_transform_rule(next_config, project_path).await?,
302 get_emotion_transform_rule(next_config).await?,
303 get_styled_components_transform_rule(next_config).await?,
304 get_styled_jsx_transform_rule(next_config, target_browsers).await?,
305 get_react_remove_properties_transform_rule(next_config).await?,
306 get_remove_console_transform_rule(next_config).await?,
307 ]
308 .into_iter()
309 .flatten()
310 .collect();
311
312 next_client_rules.extend(additional_rules);
313
314 let postcss_transform_options = PostCssTransformOptions {
315 postcss_package: Some(
316 get_postcss_package_mapping(*project_path)
317 .to_resolved()
318 .await?,
319 ),
320 config_location: PostCssConfigLocation::ProjectPathOrLocalPath,
321 ..Default::default()
322 };
323 let postcss_foreign_transform_options = PostCssTransformOptions {
324 config_location: PostCssConfigLocation::ProjectPath,
327 ..postcss_transform_options.clone()
328 };
329 let enable_postcss_transform = Some(postcss_transform_options.resolved_cell());
330 let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell());
331
332 let source_maps = if *next_config.client_source_maps(mode).await? {
333 SourceMapsType::Full
334 } else {
335 SourceMapsType::None
336 };
337 let module_options_context = ModuleOptionsContext {
338 ecmascript: EcmascriptOptionsContext {
339 enable_typeof_window_inlining: Some(TypeofWindow::Object),
340 source_maps,
341 ..Default::default()
342 },
343 css: CssOptionsContext {
344 source_maps,
345 ..Default::default()
346 },
347 preset_env_versions: 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: next_config.optimize_package_imports().owned().await?,
352 keep_last_successful_parse: next_mode.is_development(),
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 css: CssOptionsContext {
397 minify_type: if *next_config.turbo_minify(mode).await? {
398 MinifyType::Minify {
399 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
400 }
401 } else {
402 MinifyType::NoMinify
403 },
404 ..module_options_context.css
405 },
406 rules: vec![
407 (
408 foreign_code_context_condition(next_config, project_path).await?,
409 foreign_codes_options_context.resolved_cell(),
410 ),
411 (
412 internal_assets_conditions().await?,
413 internal_context.resolved_cell(),
414 ),
415 ],
416 module_rules: next_client_rules,
417 ..module_options_context
418 }
419 .cell();
420
421 Ok(module_options_context)
422}
423
424#[turbo_tasks::function]
425pub async fn get_client_chunking_context(
426 root_path: ResolvedVc<FileSystemPath>,
427 client_root: ResolvedVc<FileSystemPath>,
428 client_root_to_root_path: ResolvedVc<RcStr>,
429 asset_prefix: ResolvedVc<Option<RcStr>>,
430 chunk_suffix_path: ResolvedVc<Option<RcStr>>,
431 environment: ResolvedVc<Environment>,
432 mode: Vc<NextMode>,
433 module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
434 minify: Vc<bool>,
435 source_maps: Vc<bool>,
436 no_mangling: Vc<bool>,
437) -> Result<Vc<Box<dyn ChunkingContext>>> {
438 let next_mode = mode.await?;
439 let mut builder = BrowserChunkingContext::builder(
440 root_path,
441 client_root,
442 client_root_to_root_path,
443 client_root,
444 client_root
445 .join("static/chunks".into())
446 .to_resolved()
447 .await?,
448 get_client_assets_path(*client_root).to_resolved().await?,
449 environment,
450 next_mode.runtime_type(),
451 )
452 .chunk_base_path(asset_prefix)
453 .chunk_suffix_path(chunk_suffix_path)
454 .minify_type(if *minify.await? {
455 MinifyType::Minify {
456 mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
457 }
458 } else {
459 MinifyType::NoMinify
460 })
461 .source_maps(if *source_maps.await? {
462 SourceMapsType::Full
463 } else {
464 SourceMapsType::None
465 })
466 .asset_base_path(asset_prefix)
467 .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript)
468 .module_id_strategy(module_id_strategy);
469
470 if next_mode.is_development() {
471 builder = builder.hot_module_replacement().use_file_source_map_uris();
472 } else {
473 builder = builder.chunking_config(
474 Vc::<EcmascriptChunkType>::default().to_resolved().await?,
475 ChunkingConfig {
476 min_chunk_size: 50_000,
477 max_chunk_count_per_group: 40,
478 max_merge_chunk_size: 200_000,
479 ..Default::default()
480 },
481 );
482 builder = builder.chunking_config(
483 Vc::<CssChunkType>::default().to_resolved().await?,
484 ChunkingConfig {
485 max_merge_chunk_size: 100_000,
486 ..Default::default()
487 },
488 );
489 builder = builder.use_content_hashing(ContentHashing::Direct { length: 16 })
490 }
491
492 Ok(Vc::upcast(builder.build()))
493}
494
495#[turbo_tasks::function]
496pub fn get_client_assets_path(client_root: Vc<FileSystemPath>) -> Vc<FileSystemPath> {
497 client_root.join("static/media".into())
498}
499
500#[turbo_tasks::function]
501pub async fn get_client_runtime_entries(
502 project_root: Vc<FileSystemPath>,
503 ty: Value<ClientContextType>,
504 mode: Vc<NextMode>,
505 next_config: Vc<NextConfig>,
506 execution_context: Vc<ExecutionContext>,
507) -> Result<Vc<RuntimeEntries>> {
508 let mut runtime_entries = vec![];
509 let resolve_options_context =
510 get_client_resolve_options_context(project_root, ty, mode, next_config, execution_context);
511
512 if mode.await?.is_development() {
513 let enable_react_refresh =
514 assert_can_resolve_react_refresh(project_root, resolve_options_context)
515 .await?
516 .as_request();
517
518 if let Some(request) = enable_react_refresh {
522 runtime_entries.push(
523 RuntimeEntry::Request(
524 request.to_resolved().await?,
525 project_root.join("_".into()).to_resolved().await?,
526 )
527 .resolved_cell(),
528 )
529 };
530 }
531
532 if matches!(*ty, ClientContextType::App { .. },) {
533 runtime_entries.push(
534 RuntimeEntry::Request(
535 Request::parse(Value::new(Pattern::Constant(
536 "next/dist/client/app-next-turbopack.js".into(),
537 )))
538 .to_resolved()
539 .await?,
540 project_root.join("_".into()).to_resolved().await?,
541 )
542 .resolved_cell(),
543 );
544 }
545
546 Ok(Vc::cell(runtime_entries))
547}