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