next_core/next_client/
context.rs

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    // A separate webpack rules will be applied to codes matching
265    // foreign_code_context_condition. This allows to import codes from
266    // node_modules that requires webpack loaders, which next-dev implicitly
267    // does by default.
268    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    // Now creates a webpack rules that applies to all codes.
282    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        // For node_modules we don't want to resolve postcss config relative to the file being
325        // compiled, instead it only uses the project root postcss config.
326        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    // node_modules context
357    let foreign_codes_options_context = ModuleOptionsContext {
358        ecmascript: EcmascriptOptionsContext {
359            enable_typeof_window_inlining: None,
360            // Ignore e.g. import(`${url}`) requests in node_modules.
361            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        // NOTE(WEB-1016) PostCSS transforms should also apply to foreign code.
369        ..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        // We don't need to resolve React Refresh for each module. Instead,
386        // we try resolve it once at the root and pass down a context to all
387        // the modules.
388        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        // It's important that React Refresh come before the regular bootstrap file,
519        // because the bootstrap contains JSX which requires Refresh's global
520        // functions to be available.
521        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}