next.js/crates/next-core/src/raw_ecmascript_module.rs
raw_ecmascript_module.rs270 lines9.9 KB
use std::{io::Write, sync::LazyLock};

use anyhow::{Result, bail};
use regex::Regex;
use smallvec::smallvec;
use tracing::Instrument;
use turbo_rcstr::rcstr;
use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc, TryJoinIterExt, ValueToString, Vc};
use turbo_tasks_fs::{FileContent, rope::Rope};
use turbopack::{ModuleAssetContext, module_options::CustomModuleType};
use turbopack_core::{
    asset::Asset,
    chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext},
    code_builder::CodeBuilder,
    compile_time_info::{
        CompileTimeDefineValue, CompileTimeInfo, DefinableNameSegmentRef, DefinableNameSegmentRefs,
        FreeVarReference,
    },
    context::AssetContext,
    ident::AssetIdent,
    module::{Module, ModuleSideEffects},
    module_graph::ModuleGraph,
    reference_type::ReferenceType,
    source::{OptionSource, Source},
    source_map::GenerateSourceMap,
};
use turbopack_ecmascript::{
    EcmascriptInputTransforms,
    chunk::{
        EcmascriptChunkItemContent, EcmascriptChunkItemOptions, EcmascriptChunkPlaceable,
        EcmascriptExports, ecmascript_chunk_item,
    },
    source_map::{extract_source_mapping_url_from_content, parse_source_map_comment},
    utils::StringifyJs,
};

#[turbo_tasks::value(shared)]
pub struct RawEcmascriptModuleType {}

#[turbo_tasks::value_impl]
impl CustomModuleType for RawEcmascriptModuleType {
    #[turbo_tasks::function]
    fn create_module(
        &self,
        source: Vc<Box<dyn Source>>,
        module_asset_context: Vc<ModuleAssetContext>,
        _reference_type: ReferenceType,
    ) -> Vc<Box<dyn Module>> {
        Vc::upcast(RawEcmascriptModule::new(
            source,
            module_asset_context.compile_time_info(),
        ))
    }

    #[turbo_tasks::function]
    fn extend_ecmascript_transforms(
        self: Vc<Self>,
        _preprocess: Vc<EcmascriptInputTransforms>,
        _main: Vc<EcmascriptInputTransforms>,
        _postprocess: Vc<EcmascriptInputTransforms>,
    ) -> Vc<Box<dyn CustomModuleType>> {
        // Just ignore them
        Vc::upcast(self)
    }
}

#[turbo_tasks::value]
pub struct RawEcmascriptModule {
    source: ResolvedVc<Box<dyn Source>>,
    compile_time_info: ResolvedVc<CompileTimeInfo>,
}

#[turbo_tasks::value_impl]
impl RawEcmascriptModule {
    #[turbo_tasks::function]
    pub fn new(
        source: ResolvedVc<Box<dyn Source>>,
        compile_time_info: ResolvedVc<CompileTimeInfo>,
    ) -> Vc<Self> {
        RawEcmascriptModule {
            source,
            compile_time_info,
        }
        .cell()
    }
}

#[turbo_tasks::value_impl]
impl Module for RawEcmascriptModule {
    #[turbo_tasks::function]
    fn ident(&self) -> Vc<AssetIdent> {
        self.source.ident().with_modifier(rcstr!("raw"))
    }

    #[turbo_tasks::function]
    fn source(&self) -> Vc<OptionSource> {
        Vc::cell(Some(self.source))
    }

    #[turbo_tasks::function]
    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
        ModuleSideEffects::SideEffectful.cell()
    }
}

#[turbo_tasks::value_impl]
impl ChunkableModule for RawEcmascriptModule {
    #[turbo_tasks::function]
    fn as_chunk_item(
        self: ResolvedVc<Self>,
        module_graph: ResolvedVc<ModuleGraph>,
        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
    }
}

#[turbo_tasks::value_impl]
impl EcmascriptChunkPlaceable for RawEcmascriptModule {
    #[turbo_tasks::function]
    fn get_exports(&self) -> Vc<EcmascriptExports> {
        EcmascriptExports::CommonJs.cell()
    }

    #[turbo_tasks::function]
    async fn chunk_item_content(
        self: Vc<Self>,
        _chunking_context: Vc<Box<dyn ChunkingContext>>,
        _module_graph: Vc<ModuleGraph>,
        _async_module_info: Option<Vc<AsyncModuleInfo>>,
        _estimated: bool,
    ) -> Result<Vc<EcmascriptChunkItemContent>> {
        let span = tracing::info_span!(
            "code generation raw module",
            name = display(self.ident().to_string().await?)
        );

        async {
            let module = self.await?;
            let source = module.source;
            let content = source.content().file_content().await?;
            let content = match &*content {
                FileContent::Content(file) => file.content(),
                FileContent::NotFound => bail!("RawEcmascriptModule content not found"),
            };

            static ENV_REGEX: LazyLock<Regex> =
                LazyLock::new(|| Regex::new(r"process\.env\.([a-zA-Z0-9_]+)").unwrap());

            let content_str = content.to_str()?;

            let mut env_vars = FxIndexSet::default();
            for (_, [name]) in ENV_REGEX.captures_iter(&content_str).map(|c| c.extract()) {
                env_vars.insert(name);
            }

            let mut code = CodeBuilder::default();
            if !env_vars.is_empty() {
                let replacements = module.compile_time_info.await?.free_var_references;
                code += "var process = {env:\n";
                writeln!(
                    code,
                    "{}",
                    StringifyJs(
                        &env_vars
                            .into_iter()
                            .map(async |name| {
                                Ok((
                                    name,
                                    if let Some(value) = replacements
                                        .get(&DefinableNameSegmentRefs(smallvec![
                                            DefinableNameSegmentRef::Name("process"),
                                            DefinableNameSegmentRef::Name("env"),
                                            DefinableNameSegmentRef::Name(name),
                                        ]))
                                        .await?
                                    {
                                        let value = match &*value {
                                            FreeVarReference::Value(
                                                CompileTimeDefineValue::String(value),
                                            ) => serde_json::Value::String(value.to_string()),
                                            FreeVarReference::Value(
                                                CompileTimeDefineValue::Bool(value),
                                            ) => serde_json::Value::Bool(*value),
                                            _ => {
                                                bail!(
                                                    "Unexpected replacement for \
                                                     process.env.{name} in RawEcmascriptModule: \
                                                     {value:?}"
                                                );
                                            }
                                        };
                                        Some(value)
                                    } else {
                                        None
                                    },
                                ))
                            })
                            .try_join()
                            .await?
                            .into_iter()
                            .collect::<FxIndexMap<_, _>>()
                    )
                )?;
                code += "};\n";
            }

            code += "(function(){\n";
            let source_mapping_url = extract_source_mapping_url_from_content(&content_str);
            let source_map = if let Some((source_map, _)) =
                parse_source_map_comment(source, source_mapping_url, &*self.ident().path().await?)
                    .await?
            {
                let source_map = source_map.generate_source_map().await?;
                source_map.as_content().map(|f| f.content().clone())
            } else {
                None
            };
            code.push_source(content, source_map);

            // Add newline in case the raw code had a comment as the last line and no final newline.
            code += "\n})();\n";

            let code = code.build();
            let source_map = if code.has_source_map() {
                let source_map = code.generate_source_map_ref(None);

                static SECTIONS_REGEX: LazyLock<Regex> =
                    LazyLock::new(|| Regex::new(r#"sections"[\s\n]*:"#).unwrap());
                Some(if !SECTIONS_REGEX.is_match(&source_map.to_str()?) {
                    // This is definitely not an index source map
                    source_map
                } else {
                    let _span = tracing::span!(
                        tracing::Level::WARN,
                        "flattening index source map in RawEcmascriptModule"
                    )
                    .entered();
                    match swc_sourcemap::lazy::decode(&source_map.to_bytes())? {
                        swc_sourcemap::lazy::DecodedMap::Regular(_) => source_map,
                        // without flattening the index map, we would get nested index source maps
                        // in the output chunks, which are apparently not
                        // supported
                        swc_sourcemap::lazy::DecodedMap::Index(source_map) => {
                            let source_map = source_map.flatten()?.into_raw_sourcemap();
                            let result = serde_json::to_vec(&source_map)?;
                            Rope::from(result)
                        }
                    }
                })
            } else {
                None
            };

            Ok(EcmascriptChunkItemContent {
                source_map,
                inner_code: code.into_source_code(),
                options: EcmascriptChunkItemOptions {
                    module_and_exports: true,
                    ..Default::default()
                },
                ..Default::default()
            }
            .cell())
        }
        .instrument(span)
        .await
    }
}
Quest for Codev2.0.0
/
SIGN IN