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