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 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 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 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 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}