next.js/crates/next-core/src/next_client/context.rs
context.rs639 lines23.2 KB
use std::collections::BTreeSet;

use anyhow::Result;
use bincode::{Decode, Encode};
use turbo_rcstr::{RcStr, rcstr};
use turbo_tasks::{ResolvedVc, TaskInput, Vc, trace::TraceRawVcs};
use turbo_tasks_fs::FileSystemPath;
use turbopack::module_options::{
    CssOptionsContext, EcmascriptOptionsContext, JsxTransformOptions, ModuleRule,
    TypescriptTransformOptions, module_options_context::ModuleOptionsContext,
    side_effect_free_packages_glob,
};
use turbopack_browser::{
    BrowserChunkingContext, CurrentChunkMethod, react_refresh::assert_can_resolve_react_refresh,
};
use turbopack_core::{
    chunk::{
        AssetSuffix, ChunkingConfig, ChunkingContext, ContentHashing, CrossOrigin, MangleType,
        MinifyType, SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
        chunk_id_strategy::ModuleIdStrategy,
    },
    compile_time_info::{CompileTimeDefines, CompileTimeInfo, FreeVarReference, FreeVarReferences},
    environment::{BrowserEnvironment, Environment, ExecutionEnvironment},
    free_var_references,
    issue::IssueSeverity,
    module_graph::binding_usage_info::OptionBindingUsageInfo,
    resolve::{parse::Request, pattern::Pattern},
};
use turbopack_css::chunk::CssChunkType;
use turbopack_ecmascript::{
    AnalyzeMode, TypeofWindow, chunk::EcmascriptChunkType, references::esm::UrlRewriteBehavior,
    transform::PresetEnvConfig,
};
use turbopack_node::{
    execution_context::ExecutionContext,
    transforms::postcss::{PostCssConfigLocation, PostCssTransformOptions},
};
use turbopack_resolve::resolve_options_context::{ResolveOptionsContext, TsConfigHandling};

use crate::{
    mode::NextMode,
    next_build::get_postcss_package_mapping,
    next_client::{
        runtime_entry::{RuntimeEntries, RuntimeEntry},
        transforms::get_next_client_transforms_rules,
    },
    next_config::NextConfig,
    next_font::local::NextFontLocalResolvePlugin,
    next_import_map::{
        get_next_client_fallback_import_map, get_next_client_import_map,
        get_next_client_resolved_map,
    },
    next_shared::{
        resolve::{ModuleFeatureReportResolvePlugin, NextSharedRuntimeResolvePlugin},
        transforms::{
            emotion::get_emotion_transform_rule,
            react_remove_properties::get_react_remove_properties_transform_rule,
            relay::get_relay_transform_rule, remove_console::get_remove_console_transform_rule,
            styled_components::get_styled_components_transform_rule,
            styled_jsx::get_styled_jsx_transform_rule,
            swc_ecma_transform_plugins::get_swc_ecma_transform_plugin_rule,
        },
        webpack_rules::{WebpackLoaderBuiltinCondition, webpack_loader_options},
    },
    transform_options::{
        get_decorators_transform_options, get_jsx_transform_options,
        get_typescript_transform_options,
    },
    util::{
        OptionEnvMap, defines, foreign_code_context_condition,
        free_var_references_with_vercel_system_env_warnings, internal_assets_conditions,
        module_styles_rule_condition, worker_forwarded_globals,
    },
};

#[turbo_tasks::function]
async fn next_client_defines(define_env: Vc<OptionEnvMap>) -> Result<Vc<CompileTimeDefines>> {
    Ok(defines(&*define_env.await?).cell())
}

#[turbo_tasks::function]
async fn next_client_free_vars(
    define_env: Vc<OptionEnvMap>,
    report_system_env_inlining: Vc<IssueSeverity>,
) -> Result<Vc<FreeVarReferences>> {
    Ok(free_var_references!(
        ..free_var_references_with_vercel_system_env_warnings(
            defines(&*define_env.await?),
            *report_system_env_inlining.await?
        ),
        Buffer = FreeVarReference::EcmaScriptModule {
            request: rcstr!("node:buffer"),
            lookup_path: None,
            export: Some(rcstr!("Buffer")),
        },
        process = FreeVarReference::EcmaScriptModule {
            request: rcstr!("node:process"),
            lookup_path: None,
            export: Some(rcstr!("default")),
        }
    )
    .cell())
}

#[turbo_tasks::function]
pub async fn get_client_compile_time_info(
    browserslist_query: RcStr,
    define_env: Vc<OptionEnvMap>,
    report_system_env_inlining: Vc<IssueSeverity>,
    hot_module_replacement_enabled: bool,
) -> Result<Vc<CompileTimeInfo>> {
    CompileTimeInfo::builder(
        Environment::new(ExecutionEnvironment::Browser(
            BrowserEnvironment {
                dom: true,
                web_worker: false,
                service_worker: false,
                browserslist_query: browserslist_query.to_owned(),
            }
            .resolved_cell(),
        ))
        .to_resolved()
        .await?,
    )
    .defines(next_client_defines(define_env).to_resolved().await?)
    .free_var_references(
        next_client_free_vars(define_env, report_system_env_inlining)
            .to_resolved()
            .await?,
    )
    .hot_module_replacement_enabled(hot_module_replacement_enabled)
    .cell()
    .await
}

#[turbo_tasks::value(shared)]
#[derive(Debug, Clone, Hash, TaskInput)]
pub enum ClientContextType {
    Pages { pages_dir: FileSystemPath },
    App { app_dir: FileSystemPath },
    Fallback,
    Other,
}

#[turbo_tasks::function]
pub async fn get_client_resolve_options_context(
    project_path: FileSystemPath,
    ty: ClientContextType,
    mode: Vc<NextMode>,
    next_config: Vc<NextConfig>,
    execution_context: Vc<ExecutionContext>,
) -> Result<Vc<ResolveOptionsContext>> {
    let next_client_import_map = get_next_client_import_map(
        project_path.clone(),
        ty.clone(),
        next_config,
        mode,
        execution_context,
    )
    .to_resolved()
    .await?;
    let next_client_fallback_import_map = get_next_client_fallback_import_map(ty.clone())
        .to_resolved()
        .await?;
    let next_client_resolved_map =
        get_next_client_resolved_map(project_path.clone(), project_path.clone(), *mode.await?)
            .to_resolved()
            .await?;
    let mut custom_conditions: Vec<_> = mode.await?.custom_resolve_conditions().collect();

    if *next_config.enable_cache_components().await? {
        custom_conditions.push(rcstr!("next-js"));
    };

    let resolve_options_context = ResolveOptionsContext {
        enable_node_modules: Some(project_path.root().owned().await?),
        custom_conditions,
        import_map: Some(next_client_import_map),
        fallback_import_map: Some(next_client_fallback_import_map),
        resolved_map: Some(next_client_resolved_map),
        browser: true,
        module: true,
        before_resolve_plugins: vec![
            ResolvedVc::upcast(
                ModuleFeatureReportResolvePlugin::new(project_path.clone())
                    .to_resolved()
                    .await?,
            ),
            ResolvedVc::upcast(
                NextFontLocalResolvePlugin::new(project_path.clone())
                    .to_resolved()
                    .await?,
            ),
        ],
        after_resolve_plugins: vec![ResolvedVc::upcast(
            NextSharedRuntimeResolvePlugin::new(project_path.clone())
                .to_resolved()
                .await?,
        )],
        ..Default::default()
    };

    let tsconfig_path = next_config.typescript_tsconfig_path().await?;
    let tsconfig_path = project_path.join(
        tsconfig_path
            .as_ref()
            // Fall back to tsconfig only for resolving. This is because we don't want Turbopack to
            // resolve tsconfig.json relative to the file being compiled.
            .unwrap_or(&rcstr!("tsconfig.json")),
    )?;

    Ok(ResolveOptionsContext {
        enable_typescript: true,
        enable_react: true,
        enable_mjs_extension: true,
        custom_extensions: next_config.resolve_extension().owned().await?,
        tsconfig_path: TsConfigHandling::Fixed(tsconfig_path),
        rules: vec![(
            foreign_code_context_condition(next_config, project_path).await?,
            resolve_options_context.clone().resolved_cell(),
        )],
        ..resolve_options_context
    }
    .cell())
}

#[turbo_tasks::function]
pub async fn get_client_module_options_context(
    project_path: FileSystemPath,
    execution_context: ResolvedVc<ExecutionContext>,
    env: ResolvedVc<Environment>,
    ty: ClientContextType,
    mode: Vc<NextMode>,
    next_config: Vc<NextConfig>,
    encryption_key: ResolvedVc<RcStr>,
) -> Result<Vc<ModuleOptionsContext>> {
    let next_mode = mode.await?;
    let resolve_options_context = get_client_resolve_options_context(
        project_path.clone(),
        ty.clone(),
        mode,
        next_config,
        *execution_context,
    );

    let tsconfig_path = next_config
        .typescript_tsconfig_path()
        .await?
        .as_ref()
        .map(|p| project_path.join(p))
        .transpose()?;

    let tsconfig = get_typescript_transform_options(project_path.clone(), tsconfig_path.clone())
        .to_resolved()
        .await?;
    let decorators_options =
        get_decorators_transform_options(project_path.clone(), tsconfig_path.clone());
    let enable_mdx_rs = *next_config.mdx_rs().await?;
    let jsx_runtime_options = get_jsx_transform_options(
        project_path.clone(),
        mode,
        Some(resolve_options_context),
        false,
        next_config,
        tsconfig_path,
    )
    .to_resolved()
    .await?;

    let mut loader_conditions = BTreeSet::new();
    loader_conditions.insert(WebpackLoaderBuiltinCondition::Browser);
    loader_conditions.extend(mode.await?.webpack_loader_conditions());

    // A separate webpack rules will be applied to codes matching foreign_code_context_condition.
    // This allows to import codes from node_modules that requires webpack loaders, which next-dev
    // implicitly does by default.
    let mut foreign_conditions = loader_conditions.clone();
    foreign_conditions.insert(WebpackLoaderBuiltinCondition::Foreign);
    let foreign_enable_webpack_loaders =
        *webpack_loader_options(project_path.clone(), next_config, foreign_conditions).await?;

    // Now creates a webpack rules that applies to all code.
    let enable_webpack_loaders =
        *webpack_loader_options(project_path.clone(), next_config, loader_conditions).await?;

    let tree_shaking_mode_for_user_code = *next_config
        .tree_shaking_mode_for_user_code(next_mode.is_development())
        .await?;
    let tree_shaking_mode_for_foreign_code = *next_config
        .tree_shaking_mode_for_foreign_code(next_mode.is_development())
        .await?;
    let target_browsers = env.runtime_versions();

    let mut next_client_rules =
        get_next_client_transforms_rules(next_config, ty.clone(), mode, false, encryption_key)
            .await?;
    let foreign_next_client_rules =
        get_next_client_transforms_rules(next_config, ty.clone(), mode, true, encryption_key)
            .await?;
    let additional_rules: Vec<ModuleRule> = vec![
        get_swc_ecma_transform_plugin_rule(next_config, project_path.clone()).await?,
        get_relay_transform_rule(next_config, project_path.clone()).await?,
        get_emotion_transform_rule(next_config).await?,
        get_styled_components_transform_rule(next_config).await?,
        get_styled_jsx_transform_rule(next_config, target_browsers).await?,
        get_react_remove_properties_transform_rule(next_config).await?,
        get_remove_console_transform_rule(next_config).await?,
    ]
    .into_iter()
    .flatten()
    .collect();

    next_client_rules.extend(additional_rules);

    let local_postcss_config = *next_config
        .experimental_turbopack_local_postcss_config()
        .await?;
    let postcss_config_location = if local_postcss_config == Some(true) {
        PostCssConfigLocation::LocalPathOrProjectPath
    } else {
        PostCssConfigLocation::ProjectPathOrLocalPath
    };
    let postcss_transform_options = PostCssTransformOptions {
        postcss_package: Some(
            get_postcss_package_mapping(project_path.clone())
                .to_resolved()
                .await?,
        ),
        config_location: postcss_config_location,
        ..Default::default()
    };
    let postcss_foreign_transform_options = PostCssTransformOptions {
        // For node_modules we don't want to resolve postcss config relative to the file being
        // compiled, instead it only uses the project root postcss config.
        config_location: PostCssConfigLocation::ProjectPath,
        ..postcss_transform_options.clone()
    };
    let enable_postcss_transform = Some(postcss_transform_options.resolved_cell());
    let enable_foreign_postcss_transform = Some(postcss_foreign_transform_options.resolved_cell());

    let source_maps = *next_config.client_source_maps(mode).await?;

    let preset_env_config = (*next_config.experimental_swc_env_options().await?)
        .as_ref()
        .map(|opts| {
            PresetEnvConfig {
                mode: opts.mode.clone(),
                core_js: opts.core_js.clone(),
                skip: opts.skip.clone(),
                include: opts.include.clone(),
                exclude: opts.exclude.clone(),
                shipped_proposals: opts.shipped_proposals,
                force_all_transforms: opts.force_all_transforms,
                debug: opts.debug,
                loose: opts.loose,
            }
            .resolved_cell()
        });

    let module_options_context = ModuleOptionsContext {
        ecmascript: EcmascriptOptionsContext {
            esm_url_rewrite_behavior: Some(UrlRewriteBehavior::Relative),
            enable_typeof_window_inlining: Some(TypeofWindow::Object),
            enable_import_as_bytes: *next_config.turbopack_import_type_bytes().await?,
            enable_import_as_text: *next_config.turbopack_import_type_text().await?,
            source_maps,
            infer_module_side_effects: *next_config.turbopack_infer_module_side_effects().await?,
            preset_env_config,
            ..Default::default()
        },
        css: CssOptionsContext {
            source_maps,
            module_css_condition: Some(module_styles_rule_condition()),
            lightningcss_features: *next_config.lightningcss_feature_flags().await?,
            ..Default::default()
        },
        static_url_tag: Some(rcstr!("client")),
        environment: Some(env),
        execution_context: Some(execution_context),
        tree_shaking_mode: tree_shaking_mode_for_user_code,
        enable_postcss_transform,
        side_effect_free_packages: Some(
            side_effect_free_packages_glob(next_config.optimize_package_imports())
                .to_resolved()
                .await?,
        ),
        keep_last_successful_parse: next_mode.is_development(),
        analyze_mode: if next_mode.is_development() {
            AnalyzeMode::CodeGeneration
        } else {
            // Technically, this doesn't need to tracing for the client context. But this will
            // result in more cache hits for the analysis for modules which are loaded for both ssr
            // and client
            AnalyzeMode::CodeGenerationAndTracing
        },
        ..Default::default()
    };

    // node_modules context
    let foreign_codes_options_context = ModuleOptionsContext {
        ecmascript: EcmascriptOptionsContext {
            enable_typeof_window_inlining: None,
            // Ignore e.g. import(`${url}`) requests in node_modules.
            ignore_dynamic_requests: true,
            // Don't inject core-js polyfills into node_modules — only user code
            // should be processed by preset_env's usage/entry mode.
            preset_env_config: None,
            ..module_options_context.ecmascript
        },
        enable_webpack_loaders: foreign_enable_webpack_loaders,
        enable_postcss_transform: enable_foreign_postcss_transform,
        module_rules: foreign_next_client_rules,
        tree_shaking_mode: tree_shaking_mode_for_foreign_code,
        // NOTE(WEB-1016) PostCSS transforms should also apply to foreign code.
        ..module_options_context.clone()
    };

    let internal_context = ModuleOptionsContext {
        ecmascript: EcmascriptOptionsContext {
            enable_typescript_transform: Some(
                TypescriptTransformOptions::default().resolved_cell(),
            ),
            enable_jsx: Some(JsxTransformOptions::default().resolved_cell()),
            // Don't inject core-js polyfills into framework internals.
            preset_env_config: None,
            ..module_options_context.ecmascript.clone()
        },
        enable_postcss_transform: None,
        ..module_options_context.clone()
    };

    let module_options_context = ModuleOptionsContext {
        // We don't need to resolve React Refresh for each module. Instead,
        // we try resolve it once at the root and pass down a context to all
        // the modules.
        ecmascript: EcmascriptOptionsContext {
            enable_jsx: Some(jsx_runtime_options),
            enable_typescript_transform: Some(tsconfig),
            enable_decorators: Some(decorators_options.to_resolved().await?),
            ..module_options_context.ecmascript.clone()
        },
        enable_webpack_loaders,
        enable_mdx_rs,
        rules: vec![
            (
                foreign_code_context_condition(next_config, project_path).await?,
                foreign_codes_options_context.resolved_cell(),
            ),
            (
                internal_assets_conditions().await?,
                internal_context.resolved_cell(),
            ),
        ],
        module_rules: next_client_rules,
        ..module_options_context
    }
    .cell();

    Ok(module_options_context)
}

#[derive(Clone, Debug, PartialEq, Eq, Hash, TaskInput, TraceRawVcs, Encode, Decode)]
pub struct ClientChunkingContextOptions {
    pub mode: Vc<NextMode>,
    pub root_path: FileSystemPath,
    pub client_root: FileSystemPath,
    pub client_root_to_root_path: RcStr,
    pub client_static_folder_name: RcStr,
    pub asset_prefix: Vc<RcStr>,
    pub environment: Vc<Environment>,
    pub module_id_strategy: Vc<ModuleIdStrategy>,
    pub export_usage: Vc<OptionBindingUsageInfo>,
    pub unused_references: Vc<UnusedReferences>,
    pub minify: Vc<bool>,
    pub source_maps: Vc<SourceMapsType>,
    pub no_mangling: Vc<bool>,
    pub scope_hoisting: Vc<bool>,
    pub nested_async_chunking: Vc<bool>,
    pub debug_ids: Vc<bool>,
    pub worker_asset_prefix: Vc<Option<RcStr>>,
    pub should_use_absolute_url_references: Vc<bool>,
    pub css_url_suffix: Vc<Option<RcStr>>,
    pub hash_salt: ResolvedVc<RcStr>,
    pub cross_origin: Vc<CrossOrigin>,
}

#[turbo_tasks::function]
pub async fn get_client_chunking_context(
    options: ClientChunkingContextOptions,
) -> Result<Vc<Box<dyn ChunkingContext>>> {
    let ClientChunkingContextOptions {
        mode,
        root_path,
        client_root,
        client_root_to_root_path,
        client_static_folder_name,
        asset_prefix,
        environment,
        module_id_strategy,
        export_usage,
        unused_references,
        minify,
        source_maps,
        no_mangling,
        scope_hoisting,
        nested_async_chunking,
        debug_ids,
        worker_asset_prefix,
        should_use_absolute_url_references,
        css_url_suffix,
        hash_salt,
        cross_origin,
    } = options;

    let next_mode = mode.await?;
    let asset_prefix = asset_prefix.owned().await?;
    let cross_origin_loading = *cross_origin.await?;
    let mut builder = BrowserChunkingContext::builder(
        root_path,
        client_root.clone(),
        client_root_to_root_path,
        client_root.clone(),
        client_root
            .join(&client_static_folder_name)?
            .join("chunks")?,
        client_root
            .join(&client_static_folder_name)?
            .join("media")?,
        environment.to_resolved().await?,
        next_mode.runtime_type(),
    )
    .chunk_base_path(Some(asset_prefix.clone()))
    .asset_suffix(AssetSuffix::Inferred.resolved_cell())
    .minify_type(if *minify.await? {
        MinifyType::Minify {
            mangle: (!*no_mangling.await?).then_some(MangleType::OptimalSize),
        }
    } else {
        MinifyType::NoMinify
    })
    .source_maps(*source_maps.await?)
    .asset_base_path(Some(asset_prefix))
    .current_chunk_method(CurrentChunkMethod::DocumentCurrentScript)
    .cross_origin(cross_origin_loading)
    .export_usage(*export_usage.await?)
    .unused_references(unused_references.to_resolved().await?)
    .module_id_strategy(module_id_strategy.to_resolved().await?)
    .debug_ids(*debug_ids.await?)
    .worker_asset_prefix(worker_asset_prefix.owned().await?)
    .should_use_absolute_url_references(*should_use_absolute_url_references.await?)
    .nested_async_availability(*nested_async_chunking.await?)
    .worker_forwarded_globals(worker_forwarded_globals())
    .hash_salt(hash_salt)
    .default_url_behavior(UrlBehavior {
        suffix: AssetSuffix::Inferred,
        static_suffix: css_url_suffix.to_resolved().await?,
    });

    if next_mode.is_development() {
        builder = builder
            .hot_module_replacement()
            .source_map_source_type(SourceMapSourceType::AbsoluteFileUri)
            .dynamic_chunk_content_loading(true);
    } else {
        builder = builder
            .chunking_config(
                Vc::<EcmascriptChunkType>::default().to_resolved().await?,
                ChunkingConfig {
                    min_chunk_size: 50_000,
                    max_chunk_count_per_group: 40,
                    max_merge_chunk_size: 200_000,
                    ..Default::default()
                },
            )
            .chunking_config(
                Vc::<CssChunkType>::default().to_resolved().await?,
                ChunkingConfig {
                    max_merge_chunk_size: 100_000,
                    ..Default::default()
                },
            )
            .chunk_content_hashing(ContentHashing::Direct { length: 13 })
            .module_merging(*scope_hoisting.await?);
    }

    Ok(Vc::upcast(builder.build()))
}

#[turbo_tasks::function]
pub async fn get_client_runtime_entries(
    project_root: FileSystemPath,
    ty: ClientContextType,
    mode: Vc<NextMode>,
    next_config: Vc<NextConfig>,
    execution_context: Vc<ExecutionContext>,
) -> Result<Vc<RuntimeEntries>> {
    let mut runtime_entries = vec![];
    let resolve_options_context = get_client_resolve_options_context(
        project_root.clone(),
        ty.clone(),
        mode,
        next_config,
        execution_context,
    );

    if mode.await?.is_development() {
        let enable_react_refresh =
            assert_can_resolve_react_refresh(project_root.clone(), resolve_options_context)
                .await?
                .as_request();

        // It's important that React Refresh come before the regular bootstrap file,
        // because the bootstrap contains JSX which requires Refresh's global
        // functions to be available.
        if let Some(request) = enable_react_refresh {
            runtime_entries.push(
                RuntimeEntry::Request(request.to_resolved().await?, project_root.join("_")?)
                    .resolved_cell(),
            )
        };
    }

    if matches!(ty, ClientContextType::App { .. },) {
        runtime_entries.push(
            RuntimeEntry::Request(
                Request::parse(Pattern::Constant(rcstr!(
                    "next/dist/client/app-next-turbopack.js"
                )))
                .to_resolved()
                .await?,
                project_root.join("_")?,
            )
            .resolved_cell(),
        );
    }

    Ok(Vc::cell(runtime_entries))
}
Quest for Codev2.0.0
/
SIGN IN