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