1use std::io::Write;
2
3use anyhow::{Result, bail};
4use either::Either;
5use once_cell::sync::Lazy;
6use regex::Regex;
7use tracing::Instrument;
8use turbo_rcstr::rcstr;
9use turbo_tasks::{FxIndexMap, FxIndexSet, ResolvedVc, TryJoinIterExt, ValueToString, Vc};
10use turbo_tasks_fs::{FileContent, glob::Glob, rope::Rope};
11use turbopack::{ModuleAssetContext, module_options::CustomModuleType};
12use turbopack_core::{
13 asset::{Asset, AssetContent},
14 chunk::{ChunkItem, ChunkType, ChunkableModule, ChunkingContext},
15 code_builder::CodeBuilder,
16 compile_time_info::{
17 CompileTimeDefineValue, CompileTimeInfo, DefinableNameSegment, FreeVarReference,
18 },
19 context::AssetContext,
20 ident::AssetIdent,
21 module::Module,
22 module_graph::ModuleGraph,
23 resolve::ModulePart,
24 source::Source,
25 source_map::GenerateSourceMap,
26};
27use turbopack_ecmascript::{
28 EcmascriptInputTransforms,
29 chunk::{
30 EcmascriptChunkItem, EcmascriptChunkItemContent, EcmascriptChunkItemOptions,
31 EcmascriptChunkPlaceable, EcmascriptChunkType, EcmascriptExports,
32 },
33 source_map::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 _part: Option<ModulePart>,
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 fn ident(&self) -> Vc<AssetIdent> {
92 self.source.ident().with_modifier(rcstr!("raw"))
93 }
94}
95
96#[turbo_tasks::value_impl]
97impl Asset for RawEcmascriptModule {
98 #[turbo_tasks::function]
99 fn content(&self) -> Vc<AssetContent> {
100 self.source.content()
101 }
102}
103
104#[turbo_tasks::value_impl]
105impl ChunkableModule for RawEcmascriptModule {
106 #[turbo_tasks::function]
107 fn as_chunk_item(
108 self: ResolvedVc<Self>,
109 _module_graph: Vc<ModuleGraph>,
110 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
111 ) -> Vc<Box<dyn turbopack_core::chunk::ChunkItem>> {
112 Vc::upcast(
113 RawEcmascriptChunkItem {
114 module: self,
115 chunking_context,
116 }
117 .cell(),
118 )
119 }
120}
121
122#[turbo_tasks::value_impl]
123impl EcmascriptChunkPlaceable for RawEcmascriptModule {
124 #[turbo_tasks::function]
125 fn get_exports(&self) -> Vc<EcmascriptExports> {
126 EcmascriptExports::CommonJs.cell()
127 }
128
129 #[turbo_tasks::function]
130 fn is_marked_as_side_effect_free(&self, _side_effect_free_packages: Vc<Glob>) -> Vc<bool> {
131 Vc::cell(false)
132 }
133}
134
135#[turbo_tasks::value]
136struct RawEcmascriptChunkItem {
137 module: ResolvedVc<RawEcmascriptModule>,
138 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
139}
140
141#[turbo_tasks::value_impl]
142impl ChunkItem for RawEcmascriptChunkItem {
143 #[turbo_tasks::function]
144 fn asset_ident(&self) -> Vc<AssetIdent> {
145 self.module.ident()
146 }
147
148 #[turbo_tasks::function]
149 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
150 *self.chunking_context
151 }
152
153 #[turbo_tasks::function]
154 async fn ty(&self) -> Result<Vc<Box<dyn ChunkType>>> {
155 Ok(Vc::upcast(
156 Vc::<EcmascriptChunkType>::default().resolve().await?,
157 ))
158 }
159
160 #[turbo_tasks::function]
161 fn module(&self) -> Vc<Box<dyn Module>> {
162 Vc::upcast(*self.module)
163 }
164}
165
166#[turbo_tasks::value_impl]
167impl EcmascriptChunkItem for RawEcmascriptChunkItem {
168 #[turbo_tasks::function]
169 async fn content(&self) -> Result<Vc<EcmascriptChunkItemContent>> {
170 let span = tracing::info_span!(
171 "code generation raw module",
172 name = display(self.module.ident().to_string().await?)
173 );
174
175 async {
176 let module = self.module.await?;
177 let source = module.source;
178 let content = source.content().file_content().await?;
179 let content = match &*content {
180 FileContent::Content(file) => file.content(),
181 FileContent::NotFound => bail!("RawEcmascriptModule content not found"),
182 };
183
184 static ENV_REGEX: Lazy<Regex> =
185 Lazy::new(|| Regex::new(r"process\.env\.([a-zA-Z0-9_]+)").unwrap());
186
187 let content_str = content.to_str()?;
188
189 let mut env_vars = FxIndexSet::default();
190 for (_, [name]) in ENV_REGEX.captures_iter(&content_str).map(|c| c.extract()) {
191 env_vars.insert(name);
192 }
193
194 let mut code = CodeBuilder::default();
195 if !env_vars.is_empty() {
196 let replacements = module
197 .compile_time_info
198 .await?
199 .free_var_references
200 .individual()
201 .await?;
202 code += "var process = {env:\n";
203 writeln!(
204 code,
205 "{}",
206 StringifyJs(
207 &env_vars
208 .into_iter()
209 .map(async |name| {
210 Ok((
211 name,
212 if let Some(value) =
213 replacements.get(&DefinableNameSegment::Name(name.into()))
214 && let Some((_, value)) = value.iter().find(|(path, _)| {
215 matches!(
216 path.as_slice(),
217 [
218 DefinableNameSegment::Name(a),
219 DefinableNameSegment::Name(b)
220 ] if a == "process" && b == "env"
221 )
222 })
223 {
224 let value = value.await?;
225 let value = match &*value {
226 FreeVarReference::Value(
227 CompileTimeDefineValue::String(value),
228 ) => serde_json::Value::String(value.to_string()),
229 FreeVarReference::Value(
230 CompileTimeDefineValue::Bool(value),
231 ) => serde_json::Value::Bool(*value),
232 _ => {
233 bail!(
234 "Unexpected replacement for \
235 process.env.{name} in RawEcmascriptModule: \
236 {value:?}"
237 );
238 }
239 };
240 Some(value)
241 } else {
242 None
243 },
244 ))
245 })
246 .try_join()
247 .await?
248 .into_iter()
249 .collect::<FxIndexMap<_, _>>()
250 )
251 )?;
252 code += "};\n";
253 }
254
255 code += "(function(){\n";
256 let source_map = if let Some((source_map, _)) = parse_source_map_comment(
257 source,
258 Either::Right(&content_str),
259 &*self.module.ident().path().await?,
260 )
261 .await?
262 {
263 source_map.generate_source_map().owned().await?
264 } else {
265 None
266 };
267 code.push_source(content, source_map);
268
269 code += "\n})();\n";
271
272 let code = code.build();
273 let source_map = if code.has_source_map() {
274 let source_map = code.generate_source_map_ref(None);
275
276 static SECTIONS_REGEX: Lazy<Regex> =
277 Lazy::new(|| Regex::new(r#"sections"[\s\n]*:"#).unwrap());
278 Some(if !SECTIONS_REGEX.is_match(&source_map.to_str()?) {
279 source_map
281 } else {
282 let _span = tracing::span!(
283 tracing::Level::WARN,
284 "flattening index source map in RawEcmascriptModule"
285 )
286 .entered();
287 match swc_sourcemap::lazy::decode(&source_map.to_bytes())? {
288 swc_sourcemap::lazy::DecodedMap::Regular(_) => source_map,
289 swc_sourcemap::lazy::DecodedMap::Index(source_map) => {
293 let source_map = source_map.flatten()?.into_raw_sourcemap();
294 let result = serde_json::to_vec(&source_map)?;
295 Rope::from(result)
296 }
297 }
298 })
299 } else {
300 None
301 };
302
303 Ok(EcmascriptChunkItemContent {
304 source_map,
305 inner_code: code.into_source_code(),
306 options: EcmascriptChunkItemOptions {
307 module_and_exports: true,
308 ..Default::default()
309 },
310 ..Default::default()
311 }
312 .into())
313 }
314 .instrument(span)
315 .await
316 }
317}