Skip to main content

next_core/
raw_ecmascript_module.rs

1use std::io::Write;
2
3use anyhow::{Result, bail};
4use once_cell::sync::Lazy;
5use regex::Regex;
6use smallvec::smallvec;
7use tracing::Instrument;
8use turbo_rcstr::rcstr;
9use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc, TryJoinIterExt, ValueToString, Vc};
10use turbo_tasks_fs::{FileContent, rope::Rope};
11use turbopack::{ModuleAssetContext, module_options::CustomModuleType};
12use turbopack_core::{
13    asset::Asset,
14    chunk::{AsyncModuleInfo, ChunkableModule, ChunkingContext},
15    code_builder::CodeBuilder,
16    compile_time_info::{
17        CompileTimeDefineValue, CompileTimeInfo, DefinableNameSegmentRef, DefinableNameSegmentRefs,
18        FreeVarReference,
19    },
20    context::AssetContext,
21    ident::AssetIdent,
22    module::{Module, ModuleSideEffects},
23    module_graph::ModuleGraph,
24    resolve::ModulePart,
25    source::{OptionSource, Source},
26    source_map::GenerateSourceMap,
27};
28use turbopack_ecmascript::{
29    EcmascriptInputTransforms,
30    chunk::{
31        EcmascriptChunkItemContent, EcmascriptChunkItemOptions, EcmascriptChunkPlaceable,
32        EcmascriptExports, ecmascript_chunk_item,
33    },
34    source_map::{extract_source_mapping_url_from_content, parse_source_map_comment},
35    utils::StringifyJs,
36};
37
38#[turbo_tasks::value(shared)]
39pub struct RawEcmascriptModuleType {}
40
41#[turbo_tasks::value_impl]
42impl CustomModuleType for RawEcmascriptModuleType {
43    #[turbo_tasks::function]
44    fn create_module(
45        &self,
46        source: Vc<Box<dyn Source>>,
47        module_asset_context: Vc<ModuleAssetContext>,
48        _part: Option<ModulePart>,
49    ) -> Vc<Box<dyn Module>> {
50        Vc::upcast(RawEcmascriptModule::new(
51            source,
52            module_asset_context.compile_time_info(),
53        ))
54    }
55
56    #[turbo_tasks::function]
57    fn extend_ecmascript_transforms(
58        self: Vc<Self>,
59        _preprocess: Vc<EcmascriptInputTransforms>,
60        _main: Vc<EcmascriptInputTransforms>,
61        _postprocess: Vc<EcmascriptInputTransforms>,
62    ) -> Vc<Box<dyn CustomModuleType>> {
63        // Just ignore them
64        Vc::upcast(self)
65    }
66}
67
68#[turbo_tasks::value]
69pub struct RawEcmascriptModule {
70    source: ResolvedVc<Box<dyn Source>>,
71    compile_time_info: ResolvedVc<CompileTimeInfo>,
72}
73
74#[turbo_tasks::value_impl]
75impl RawEcmascriptModule {
76    #[turbo_tasks::function]
77    pub fn new(
78        source: ResolvedVc<Box<dyn Source>>,
79        compile_time_info: ResolvedVc<CompileTimeInfo>,
80    ) -> Vc<Self> {
81        RawEcmascriptModule {
82            source,
83            compile_time_info,
84        }
85        .cell()
86    }
87}
88
89#[turbo_tasks::value_impl]
90impl Module for RawEcmascriptModule {
91    #[turbo_tasks::function]
92    fn ident(&self) -> Vc<AssetIdent> {
93        self.source.ident().with_modifier(rcstr!("raw"))
94    }
95
96    #[turbo_tasks::function]
97    fn source(&self) -> Vc<OptionSource> {
98        Vc::cell(Some(self.source))
99    }
100
101    #[turbo_tasks::function]
102    fn side_effects(self: Vc<Self>) -> Vc<ModuleSideEffects> {
103        ModuleSideEffects::SideEffectful.cell()
104    }
105}
106
107#[turbo_tasks::value_impl]
108impl ChunkableModule for RawEcmascriptModule {
109    #[turbo_tasks::function]
110    fn as_chunk_item(
111        self: ResolvedVc<Self>,
112        module_graph: ResolvedVc<ModuleGraph>,
113        chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
114    ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
115        ecmascript_chunk_item(ResolvedVc::upcast(self), module_graph, chunking_context)
116    }
117}
118
119#[turbo_tasks::value_impl]
120impl EcmascriptChunkPlaceable for RawEcmascriptModule {
121    #[turbo_tasks::function]
122    fn get_exports(&self) -> Vc<EcmascriptExports> {
123        EcmascriptExports::CommonJs.cell()
124    }
125
126    #[turbo_tasks::function]
127    async fn chunk_item_content(
128        self: Vc<Self>,
129        _chunking_context: Vc<Box<dyn ChunkingContext>>,
130        _module_graph: Vc<ModuleGraph>,
131        _async_module_info: Option<Vc<AsyncModuleInfo>>,
132        _estimated: bool,
133    ) -> Result<Vc<EcmascriptChunkItemContent>> {
134        let span = tracing::info_span!(
135            "code generation raw module",
136            name = display(self.ident().to_string().await?)
137        );
138
139        async {
140            let module = self.await?;
141            let source = module.source;
142            let content = source.content().file_content().await?;
143            let content = match &*content {
144                FileContent::Content(file) => file.content(),
145                FileContent::NotFound => bail!("RawEcmascriptModule content not found"),
146            };
147
148            static ENV_REGEX: Lazy<Regex> =
149                Lazy::new(|| Regex::new(r"process\.env\.([a-zA-Z0-9_]+)").unwrap());
150
151            let content_str = content.to_str()?;
152
153            let mut env_vars = FxIndexSet::default();
154            for (_, [name]) in ENV_REGEX.captures_iter(&content_str).map(|c| c.extract()) {
155                env_vars.insert(name);
156            }
157
158            let mut code = CodeBuilder::default();
159            if !env_vars.is_empty() {
160                let replacements = module.compile_time_info.await?.free_var_references;
161                code += "var process = {env:\n";
162                writeln!(
163                    code,
164                    "{}",
165                    StringifyJs(
166                        &env_vars
167                            .into_iter()
168                            .map(async |name| {
169                                Ok((
170                                    name,
171                                    if let Some(value) = replacements
172                                        .get(&DefinableNameSegmentRefs(smallvec![
173                                            DefinableNameSegmentRef::Name("process"),
174                                            DefinableNameSegmentRef::Name("env"),
175                                            DefinableNameSegmentRef::Name(name),
176                                        ]))
177                                        .await?
178                                    {
179                                        let value = match &*value {
180                                            FreeVarReference::Value(
181                                                CompileTimeDefineValue::String(value),
182                                            ) => serde_json::Value::String(value.to_string()),
183                                            FreeVarReference::Value(
184                                                CompileTimeDefineValue::Bool(value),
185                                            ) => serde_json::Value::Bool(*value),
186                                            _ => {
187                                                bail!(
188                                                    "Unexpected replacement for \
189                                                     process.env.{name} in RawEcmascriptModule: \
190                                                     {value:?}"
191                                                );
192                                            }
193                                        };
194                                        Some(value)
195                                    } else {
196                                        None
197                                    },
198                                ))
199                            })
200                            .try_join()
201                            .await?
202                            .into_iter()
203                            .collect::<FxIndexMap<_, _>>()
204                    )
205                )?;
206                code += "};\n";
207            }
208
209            code += "(function(){\n";
210            let source_mapping_url = extract_source_mapping_url_from_content(&content_str);
211            let source_map = if let Some((source_map, _)) =
212                parse_source_map_comment(source, source_mapping_url, &*self.ident().path().await?)
213                    .await?
214            {
215                let source_map = source_map.generate_source_map().await?;
216                source_map.as_content().map(|f| f.content().clone())
217            } else {
218                None
219            };
220            code.push_source(content, source_map);
221
222            // Add newline in case the raw code had a comment as the last line and no final newline.
223            code += "\n})();\n";
224
225            let code = code.build();
226            let source_map = if code.has_source_map() {
227                let source_map = code.generate_source_map_ref(None);
228
229                static SECTIONS_REGEX: Lazy<Regex> =
230                    Lazy::new(|| Regex::new(r#"sections"[\s\n]*:"#).unwrap());
231                Some(if !SECTIONS_REGEX.is_match(&source_map.to_str()?) {
232                    // This is definitely not an index source map
233                    source_map
234                } else {
235                    let _span = tracing::span!(
236                        tracing::Level::WARN,
237                        "flattening index source map in RawEcmascriptModule"
238                    )
239                    .entered();
240                    match swc_sourcemap::lazy::decode(&source_map.to_bytes())? {
241                        swc_sourcemap::lazy::DecodedMap::Regular(_) => source_map,
242                        // without flattening the index map, we would get nested index source maps
243                        // in the output chunks, which are apparently not
244                        // supported
245                        swc_sourcemap::lazy::DecodedMap::Index(source_map) => {
246                            let source_map = source_map.flatten()?.into_raw_sourcemap();
247                            let result = serde_json::to_vec(&source_map)?;
248                            Rope::from(result)
249                        }
250                    }
251                })
252            } else {
253                None
254            };
255
256            Ok(EcmascriptChunkItemContent {
257                source_map,
258                inner_code: code.into_source_code(),
259                options: EcmascriptChunkItemOptions {
260                    module_and_exports: true,
261                    ..Default::default()
262                },
263                ..Default::default()
264            }
265            .cell())
266        }
267        .instrument(span)
268        .await
269    }
270}