1#![feature(min_specialization)]
2#![feature(arbitrary_self_types)]
3#![feature(arbitrary_self_types_pointers)]
4
5use std::{iter::once, thread::available_parallelism};
6
7use anyhow::{Result, bail};
8use rustc_hash::FxHashMap;
9use turbo_rcstr::{RcStr, rcstr};
10use turbo_tasks::{
11 FxIndexSet, ResolvedVc, TryJoinIterExt, Vc,
12 graph::{AdjacencyMap, GraphTraversal},
13};
14use turbo_tasks_env::ProcessEnv;
15use turbo_tasks_fs::{File, FileSystemPath, to_sys_path};
16use turbopack_core::{
17 asset::{Asset, AssetContent},
18 changed::content_changed,
19 chunk::{ChunkingContext, ChunkingContextExt, EvaluatableAsset, EvaluatableAssets},
20 module::Module,
21 module_graph::{ModuleGraph, chunk_group_info::ChunkGroupEntry},
22 output::{OutputAsset, OutputAssets, OutputAssetsSet},
23 source_map::GenerateSourceMap,
24 virtual_output::VirtualOutputAsset,
25};
26
27use self::pool::NodeJsPool;
28
29pub mod debug;
30pub mod embed_js;
31pub mod evaluate;
32pub mod execution_context;
33mod heap_queue;
34mod pool;
35pub mod route_matcher;
36pub mod source_map;
37pub mod transforms;
38
39#[turbo_tasks::function]
40async fn emit(
41 intermediate_asset: Vc<Box<dyn OutputAsset>>,
42 intermediate_output_path: FileSystemPath,
43) -> Result<()> {
44 for asset in internal_assets(intermediate_asset, intermediate_output_path).await? {
45 let _ = asset
46 .content()
47 .write(asset.path().owned().await?)
48 .resolve()
49 .await?;
50 }
51 Ok(())
52}
53
54#[derive(Debug)]
57#[turbo_tasks::value]
58struct SeparatedAssets {
59 internal_assets: ResolvedVc<OutputAssetsSet>,
60 external_asset_entrypoints: ResolvedVc<OutputAssetsSet>,
61}
62
63#[turbo_tasks::function]
67async fn internal_assets(
68 intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
69 intermediate_output_path: FileSystemPath,
70) -> Result<Vc<OutputAssetsSet>> {
71 Ok(
72 *separate_assets_operation(intermediate_asset, intermediate_output_path)
73 .read_strongly_consistent()
74 .await?
75 .internal_assets,
76 )
77}
78
79#[turbo_tasks::value(transparent)]
80pub struct AssetsForSourceMapping(FxHashMap<String, ResolvedVc<Box<dyn GenerateSourceMap>>>);
81
82#[turbo_tasks::function]
85async fn internal_assets_for_source_mapping(
86 intermediate_asset: Vc<Box<dyn OutputAsset>>,
87 intermediate_output_path: FileSystemPath,
88) -> Result<Vc<AssetsForSourceMapping>> {
89 let internal_assets =
90 internal_assets(intermediate_asset, intermediate_output_path.clone()).await?;
91 let intermediate_output_path = intermediate_output_path.clone();
92 let mut internal_assets_for_source_mapping = FxHashMap::default();
93 for asset in internal_assets.iter() {
94 if let Some(generate_source_map) =
95 ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(*asset)
96 && let Some(path) = intermediate_output_path.get_path_to(&*asset.path().await?)
97 {
98 internal_assets_for_source_mapping.insert(path.to_string(), generate_source_map);
99 }
100 }
101 Ok(Vc::cell(internal_assets_for_source_mapping))
102}
103
104#[turbo_tasks::function]
107pub async fn external_asset_entrypoints(
108 module: Vc<Box<dyn EvaluatableAsset>>,
109 runtime_entries: Vc<EvaluatableAssets>,
110 chunking_context: Vc<Box<dyn ChunkingContext>>,
111 intermediate_output_path: FileSystemPath,
112) -> Result<Vc<OutputAssetsSet>> {
113 Ok(*separate_assets_operation(
114 get_intermediate_asset(chunking_context, module, runtime_entries)
115 .to_resolved()
116 .await?,
117 intermediate_output_path,
118 )
119 .read_strongly_consistent()
120 .await?
121 .external_asset_entrypoints)
122}
123
124#[turbo_tasks::function(operation)]
127async fn separate_assets_operation(
128 intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
129 intermediate_output_path: FileSystemPath,
130) -> Result<Vc<SeparatedAssets>> {
131 let intermediate_output_path = intermediate_output_path.clone();
132 #[derive(PartialEq, Eq, Hash, Clone, Copy)]
133 enum Type {
134 Internal(ResolvedVc<Box<dyn OutputAsset>>),
135 External(ResolvedVc<Box<dyn OutputAsset>>),
136 }
137 let get_asset_children = |asset| {
138 let intermediate_output_path = intermediate_output_path.clone();
139 async move {
140 let Type::Internal(asset) = asset else {
141 return Ok(Vec::new());
142 };
143 asset
144 .references()
145 .await?
146 .iter()
147 .map(|asset| async {
148 if asset.path().await?.is_inside_ref(&intermediate_output_path) {
153 Ok(Type::Internal(*asset))
154 } else {
155 Ok(Type::External(*asset))
156 }
157 })
158 .try_join()
159 .await
160 }
161 };
162
163 let graph = AdjacencyMap::new()
164 .skip_duplicates()
165 .visit(once(Type::Internal(intermediate_asset)), get_asset_children)
166 .await
167 .completed()?
168 .into_inner();
169
170 let mut internal_assets = FxIndexSet::default();
171 let mut external_asset_entrypoints = FxIndexSet::default();
172
173 for item in graph.into_postorder_topological() {
174 match item {
175 Type::Internal(asset) => {
176 internal_assets.insert(asset);
177 }
178 Type::External(asset) => {
179 external_asset_entrypoints.insert(asset);
180 }
181 }
182 }
183
184 Ok(SeparatedAssets {
185 internal_assets: ResolvedVc::cell(internal_assets),
186 external_asset_entrypoints: ResolvedVc::cell(external_asset_entrypoints),
187 }
188 .cell())
189}
190
191fn emit_package_json(dir: FileSystemPath) -> Result<Vc<()>> {
195 Ok(emit(
196 Vc::upcast(VirtualOutputAsset::new(
197 dir.join("package.json")?,
198 AssetContent::file(File::from("{\"type\": \"commonjs\"}").into()),
199 )),
200 dir,
201 ))
202}
203
204#[turbo_tasks::function(operation)]
206pub async fn get_renderer_pool_operation(
207 cwd: FileSystemPath,
208 env: ResolvedVc<Box<dyn ProcessEnv>>,
209 intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
210 intermediate_output_path: FileSystemPath,
211 output_root: FileSystemPath,
212 project_dir: FileSystemPath,
213 debug: bool,
214) -> Result<Vc<NodeJsPool>> {
215 emit_package_json(intermediate_output_path.clone())?.await?;
216
217 emit(*intermediate_asset, output_root.clone())
218 .as_side_effect()
219 .await?;
220 let assets_for_source_mapping =
221 internal_assets_for_source_mapping(*intermediate_asset, output_root.clone());
222
223 let entrypoint = intermediate_asset.path().owned().await?;
224
225 let Some(cwd) = to_sys_path(cwd.clone()).await? else {
226 bail!(
227 "can only render from a disk filesystem, but `cwd = {}`",
228 cwd.value_to_string().await?
229 );
230 };
231 let Some(entrypoint) = to_sys_path(entrypoint.clone()).await? else {
232 bail!(
233 "can only render from a disk filesystem, but `entrypoint = {}`",
234 entrypoint.value_to_string().await?
235 );
236 };
237 content_changed(*ResolvedVc::upcast(intermediate_asset)).await?;
239
240 Ok(NodeJsPool::new(
241 cwd,
242 entrypoint,
243 env.read_all()
244 .await?
245 .iter()
246 .map(|(k, v)| (k.clone(), v.clone()))
247 .collect(),
248 assets_for_source_mapping.to_resolved().await?,
249 output_root,
250 project_dir,
251 available_parallelism().map_or(1, |v| v.get()),
252 debug,
253 )
254 .cell())
255}
256
257#[turbo_tasks::function]
259pub async fn get_intermediate_asset(
260 chunking_context: Vc<Box<dyn ChunkingContext>>,
261 main_entry: ResolvedVc<Box<dyn EvaluatableAsset>>,
262 other_entries: Vc<EvaluatableAssets>,
263) -> Result<Vc<Box<dyn OutputAsset>>> {
264 Ok(chunking_context.root_entry_chunk_group_asset(
265 chunking_context
266 .chunk_path(None, main_entry.ident(), None, rcstr!(".js"))
267 .owned()
268 .await?,
269 other_entries.with_entry(*main_entry),
270 ModuleGraph::from_modules(
271 Vc::cell(vec![ChunkGroupEntry::Entry(
272 other_entries
273 .await?
274 .into_iter()
275 .copied()
276 .chain(std::iter::once(main_entry))
277 .map(ResolvedVc::upcast)
278 .collect(),
279 )]),
280 false,
281 ),
282 OutputAssets::empty(),
283 OutputAssets::empty(),
284 ))
285}
286
287#[derive(Clone, Debug)]
288#[turbo_tasks::value(shared)]
289pub struct ResponseHeaders {
290 pub status: u16,
291 pub headers: Vec<(RcStr, RcStr)>,
292}