turbopack_node/
lib.rs

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/// List of the all assets of the "internal" subgraph and a list of boundary
55/// assets that are not considered "internal" ("external")
56#[derive(Debug)]
57#[turbo_tasks::value]
58struct SeparatedAssets {
59    internal_assets: ResolvedVc<OutputAssetsSet>,
60    external_asset_entrypoints: ResolvedVc<OutputAssetsSet>,
61}
62
63/// Extracts the subgraph of "internal" assets (assets within the passes
64/// directory). Also lists all boundary assets that are not part of the
65/// "internal" subgraph.
66#[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/// Extracts a map of "internal" assets ([`internal_assets`]) which implement
83/// the [GenerateSourceMap] trait.
84#[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/// Returns a set of "external" assets on the boundary of the "internal"
105/// subgraph
106#[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/// Splits the asset graph into "internal" assets and boundaries to "external"
125/// assets.
126#[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                    // Assets within the output directory are considered as "internal" and all
149                    // others as "external". We follow references on "internal" assets, but do not
150                    // look into references of "external" assets, since there are no "internal"
151                    // assets behind "externals"
152                    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
191/// Emit a basic package.json that sets the type of the package to commonjs.
192/// Currently code generated for Node is CommonJS, while authored code may be
193/// ESM, for example.
194fn 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/// Creates a node.js renderer pool for an entrypoint.
205#[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    // Invalidate pool when code content changes
238    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/// Converts a module graph into node.js executable assets
258#[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}