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};
8pub use node_entry::{NodeEntry, NodeRenderingEntries, NodeRenderingEntry};
9use rustc_hash::FxHashMap;
10use turbo_rcstr::{RcStr, rcstr};
11use turbo_tasks::{
12    FxIndexSet, ResolvedVc, TryJoinIterExt, Vc,
13    graph::{AdjacencyMap, GraphTraversal},
14};
15use turbo_tasks_env::ProcessEnv;
16use turbo_tasks_fs::{File, FileSystemPath, to_sys_path};
17use turbopack_core::{
18    asset::{Asset, AssetContent},
19    changed::content_changed,
20    chunk::{ChunkingContext, ChunkingContextExt, EvaluatableAsset, EvaluatableAssets},
21    module::Module,
22    module_graph::{ModuleGraph, chunk_group_info::ChunkGroupEntry},
23    output::{OutputAsset, OutputAssets, OutputAssetsSet},
24    source_map::GenerateSourceMap,
25    virtual_output::VirtualOutputAsset,
26};
27
28use self::{pool::NodeJsPool, source_map::StructuredError};
29
30pub mod debug;
31pub mod embed_js;
32pub mod evaluate;
33pub mod execution_context;
34mod node_entry;
35mod pool;
36pub mod render;
37pub mod route_matcher;
38pub mod source_map;
39pub mod transforms;
40
41#[turbo_tasks::function]
42async fn emit(
43    intermediate_asset: Vc<Box<dyn OutputAsset>>,
44    intermediate_output_path: FileSystemPath,
45) -> Result<()> {
46    for asset in internal_assets(intermediate_asset, intermediate_output_path).await? {
47        let _ = asset
48            .content()
49            .write(asset.path().await?.clone_value())
50            .resolve()
51            .await?;
52    }
53    Ok(())
54}
55
56/// List of the all assets of the "internal" subgraph and a list of boundary
57/// assets that are not considered "internal" ("external")
58#[derive(Debug)]
59#[turbo_tasks::value]
60struct SeparatedAssets {
61    internal_assets: ResolvedVc<OutputAssetsSet>,
62    external_asset_entrypoints: ResolvedVc<OutputAssetsSet>,
63}
64
65/// Extracts the subgraph of "internal" assets (assets within the passes
66/// directory). Also lists all boundary assets that are not part of the
67/// "internal" subgraph.
68#[turbo_tasks::function]
69async fn internal_assets(
70    intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
71    intermediate_output_path: FileSystemPath,
72) -> Result<Vc<OutputAssetsSet>> {
73    Ok(
74        *separate_assets_operation(intermediate_asset, intermediate_output_path)
75            .read_strongly_consistent()
76            .await?
77            .internal_assets,
78    )
79}
80
81#[turbo_tasks::value(transparent)]
82pub struct AssetsForSourceMapping(FxHashMap<String, ResolvedVc<Box<dyn GenerateSourceMap>>>);
83
84/// Extracts a map of "internal" assets ([`internal_assets`]) which implement
85/// the [GenerateSourceMap] trait.
86#[turbo_tasks::function]
87async fn internal_assets_for_source_mapping(
88    intermediate_asset: Vc<Box<dyn OutputAsset>>,
89    intermediate_output_path: FileSystemPath,
90) -> Result<Vc<AssetsForSourceMapping>> {
91    let internal_assets =
92        internal_assets(intermediate_asset, intermediate_output_path.clone()).await?;
93    let intermediate_output_path = intermediate_output_path.clone();
94    let mut internal_assets_for_source_mapping = FxHashMap::default();
95    for asset in internal_assets.iter() {
96        if let Some(generate_source_map) =
97            ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(*asset)
98            && let Some(path) = intermediate_output_path.get_path_to(&*asset.path().await?)
99        {
100            internal_assets_for_source_mapping.insert(path.to_string(), generate_source_map);
101        }
102    }
103    Ok(Vc::cell(internal_assets_for_source_mapping))
104}
105
106/// Returns a set of "external" assets on the boundary of the "internal"
107/// subgraph
108#[turbo_tasks::function]
109pub async fn external_asset_entrypoints(
110    module: Vc<Box<dyn EvaluatableAsset>>,
111    runtime_entries: Vc<EvaluatableAssets>,
112    chunking_context: Vc<Box<dyn ChunkingContext>>,
113    intermediate_output_path: FileSystemPath,
114) -> Result<Vc<OutputAssetsSet>> {
115    Ok(*separate_assets_operation(
116        get_intermediate_asset(chunking_context, module, runtime_entries)
117            .to_resolved()
118            .await?,
119        intermediate_output_path,
120    )
121    .read_strongly_consistent()
122    .await?
123    .external_asset_entrypoints)
124}
125
126/// Splits the asset graph into "internal" assets and boundaries to "external"
127/// assets.
128#[turbo_tasks::function(operation)]
129async fn separate_assets_operation(
130    intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
131    intermediate_output_path: FileSystemPath,
132) -> Result<Vc<SeparatedAssets>> {
133    let intermediate_output_path = intermediate_output_path.clone();
134    #[derive(PartialEq, Eq, Hash, Clone, Copy)]
135    enum Type {
136        Internal(ResolvedVc<Box<dyn OutputAsset>>),
137        External(ResolvedVc<Box<dyn OutputAsset>>),
138    }
139    let get_asset_children = |asset| {
140        let intermediate_output_path = intermediate_output_path.clone();
141        async move {
142            let Type::Internal(asset) = asset else {
143                return Ok(Vec::new());
144            };
145            asset
146                .references()
147                .await?
148                .iter()
149                .map(|asset| async {
150                    // Assets within the output directory are considered as "internal" and all
151                    // others as "external". We follow references on "internal" assets, but do not
152                    // look into references of "external" assets, since there are no "internal"
153                    // assets behind "externals"
154                    if asset.path().await?.is_inside_ref(&intermediate_output_path) {
155                        Ok(Type::Internal(*asset))
156                    } else {
157                        Ok(Type::External(*asset))
158                    }
159                })
160                .try_join()
161                .await
162        }
163    };
164
165    let graph = AdjacencyMap::new()
166        .skip_duplicates()
167        .visit(once(Type::Internal(intermediate_asset)), get_asset_children)
168        .await
169        .completed()?
170        .into_inner();
171
172    let mut internal_assets = FxIndexSet::default();
173    let mut external_asset_entrypoints = FxIndexSet::default();
174
175    for item in graph.into_postorder_topological() {
176        match item {
177            Type::Internal(asset) => {
178                internal_assets.insert(asset);
179            }
180            Type::External(asset) => {
181                external_asset_entrypoints.insert(asset);
182            }
183        }
184    }
185
186    Ok(SeparatedAssets {
187        internal_assets: ResolvedVc::cell(internal_assets),
188        external_asset_entrypoints: ResolvedVc::cell(external_asset_entrypoints),
189    }
190    .cell())
191}
192
193/// Emit a basic package.json that sets the type of the package to commonjs.
194/// Currently code generated for Node is CommonJS, while authored code may be
195/// ESM, for example.
196fn emit_package_json(dir: FileSystemPath) -> Result<Vc<()>> {
197    Ok(emit(
198        Vc::upcast(VirtualOutputAsset::new(
199            dir.join("package.json")?,
200            AssetContent::file(File::from("{\"type\": \"commonjs\"}").into()),
201        )),
202        dir,
203    ))
204}
205
206/// Creates a node.js renderer pool for an entrypoint.
207#[turbo_tasks::function(operation)]
208pub async fn get_renderer_pool_operation(
209    cwd: FileSystemPath,
210    env: ResolvedVc<Box<dyn ProcessEnv>>,
211    intermediate_asset: ResolvedVc<Box<dyn OutputAsset>>,
212    intermediate_output_path: FileSystemPath,
213    output_root: FileSystemPath,
214    project_dir: FileSystemPath,
215    debug: bool,
216) -> Result<Vc<NodeJsPool>> {
217    emit_package_json(intermediate_output_path.clone())?.await?;
218
219    let _ = emit(*intermediate_asset, output_root.clone())
220        .resolve()
221        .await?;
222    let assets_for_source_mapping =
223        internal_assets_for_source_mapping(*intermediate_asset, output_root.clone());
224
225    let entrypoint = intermediate_asset.path().await?.clone_value();
226
227    let Some(cwd) = to_sys_path(cwd.clone()).await? else {
228        bail!(
229            "can only render from a disk filesystem, but `cwd = {}`",
230            cwd.value_to_string().await?
231        );
232    };
233    let Some(entrypoint) = to_sys_path(entrypoint.clone()).await? else {
234        bail!(
235            "can only render from a disk filesystem, but `entrypoint = {}`",
236            entrypoint.value_to_string().await?
237        );
238    };
239    // Invalidate pool when code content changes
240    content_changed(*ResolvedVc::upcast(intermediate_asset)).await?;
241
242    Ok(NodeJsPool::new(
243        cwd,
244        entrypoint,
245        env.read_all()
246            .await?
247            .iter()
248            .map(|(k, v)| (k.clone(), v.clone()))
249            .collect(),
250        assets_for_source_mapping.to_resolved().await?,
251        output_root,
252        project_dir,
253        available_parallelism().map_or(1, |v| v.get()),
254        debug,
255    )
256    .cell())
257}
258
259/// Converts a module graph into node.js executable assets
260#[turbo_tasks::function]
261pub async fn get_intermediate_asset(
262    chunking_context: Vc<Box<dyn ChunkingContext>>,
263    main_entry: ResolvedVc<Box<dyn EvaluatableAsset>>,
264    other_entries: Vc<EvaluatableAssets>,
265) -> Result<Vc<Box<dyn OutputAsset>>> {
266    Ok(Vc::upcast(
267        chunking_context.root_entry_chunk_group_asset(
268            chunking_context
269                .chunk_path(None, main_entry.ident(), rcstr!(".js"))
270                .await?
271                .clone_value(),
272            other_entries.with_entry(*main_entry),
273            ModuleGraph::from_modules(
274                Vc::cell(vec![ChunkGroupEntry::Entry(
275                    other_entries
276                        .await?
277                        .into_iter()
278                        .copied()
279                        .chain(std::iter::once(main_entry))
280                        .map(ResolvedVc::upcast)
281                        .collect(),
282                )]),
283                false,
284            ),
285            OutputAssets::empty(),
286        ),
287    ))
288}
289
290#[derive(Clone, Debug)]
291#[turbo_tasks::value(shared)]
292pub struct ResponseHeaders {
293    pub status: u16,
294    pub headers: Vec<(RcStr, RcStr)>,
295}
296
297pub fn register() {
298    turbo_tasks::register();
299    turbo_tasks_bytes::register();
300    turbo_tasks_fs::register();
301    turbopack_dev_server::register();
302    turbopack_ecmascript::register();
303    include!(concat!(env!("OUT_DIR"), "/register.rs"));
304}