Skip to main content

turbopack_nodejs/ecmascript/node/entry/
runtime.rs

1use std::io::Write;
2
3use anyhow::Result;
4use indoc::writedoc;
5use turbo_rcstr::rcstr;
6use turbo_tasks::{ResolvedVc, ValueToString, Vc, turbobail};
7use turbo_tasks_fs::{File, FileContent, FileSystem, FileSystemPath};
8use turbopack_core::{
9    asset::{Asset, AssetContent},
10    chunk::ChunkingContext,
11    code_builder::{Code, CodeBuilder},
12    ident::AssetIdent,
13    output::{OutputAsset, OutputAssetsReference, OutputAssetsWithReferenced},
14    source_map::{GenerateSourceMap, SourceMapAsset},
15};
16use turbopack_ecmascript::utils::StringifyJs;
17use turbopack_ecmascript_runtime::RuntimeType;
18
19use crate::NodeJsChunkingContext;
20
21/// An Ecmascript chunk that contains the Node.js runtime code.
22#[turbo_tasks::value(shared)]
23#[derive(ValueToString)]
24#[value_to_string("Ecmascript Build Node Runtime Chunk")]
25pub struct EcmascriptBuildNodeRuntimeChunk {
26    chunking_context: ResolvedVc<NodeJsChunkingContext>,
27    include_async_module_runtime: bool,
28}
29
30#[turbo_tasks::value_impl]
31impl EcmascriptBuildNodeRuntimeChunk {
32    /// Creates a new [`Vc<EcmascriptBuildNodeRuntimeChunk>`].
33    #[turbo_tasks::function]
34    pub fn new(
35        chunking_context: ResolvedVc<NodeJsChunkingContext>,
36        include_async_module_runtime: bool,
37    ) -> Vc<Self> {
38        EcmascriptBuildNodeRuntimeChunk {
39            chunking_context,
40            include_async_module_runtime,
41        }
42        .cell()
43    }
44
45    #[turbo_tasks::function]
46    async fn code(self: Vc<Self>) -> Result<Vc<Code>> {
47        let this = self.await?;
48
49        let output_root_to_root_path = this.chunking_context.output_root_to_root_path().await?;
50        let output_root = this.chunking_context.output_root().await?;
51        let generate_source_map = *this
52            .chunking_context
53            .reference_chunk_source_maps(Vc::upcast(self))
54            .await?;
55        let runtime_path = self.path().await?;
56        let runtime_public_path = if let Some(path) = output_root.get_path_to(&runtime_path) {
57            path
58        } else {
59            turbobail!("runtime path {runtime_path} is not in output root {output_root}");
60        };
61
62        let mut code = CodeBuilder::default();
63        let asset_prefix = this.chunking_context.asset_prefix().await?;
64        let asset_prefix = asset_prefix.as_deref().unwrap_or("/");
65
66        // Get the list of global variable names to forward to workers
67        let worker_config = Vc::upcast::<Box<dyn ChunkingContext>>(*this.chunking_context)
68            .worker_configuration_options()
69            .await?;
70
71        writedoc!(
72            code,
73            r#"
74                var RUNTIME_PUBLIC_PATH = {};
75                var RELATIVE_ROOT_PATH = {};
76                var ASSET_PREFIX = {};
77            "#,
78            StringifyJs(runtime_public_path),
79            StringifyJs(output_root_to_root_path.as_str()),
80            StringifyJs(asset_prefix),
81        )?;
82
83        // Add preamble to read forwarded globals from workerData (for worker_threads)
84        if !worker_config.forwarded_globals.is_empty() {
85            writedoc!(
86                code,
87                r#"
88                    // Apply forwarded globals from workerData if running in a worker thread
89                    if (typeof require !== 'undefined') {{
90                        try {{
91                            var {{ workerData }} = require('worker_threads');
92                            if (workerData?.__turbopack_globals__) {{
93                                Object.assign(globalThis, workerData.__turbopack_globals__);
94                                // Remove internal data so it's not visible to user code
95                                delete workerData.__turbopack_globals__;
96                            }}
97                        }} catch (_) {{
98                            // Not in a worker thread context, ignore
99                        }}
100                    }}
101                "#,
102            )?;
103        }
104
105        let asset_context =
106            turbopack::get_runtime_asset_context(this.chunking_context.environment());
107
108        match *this.chunking_context.runtime_type().await? {
109            RuntimeType::Development => {
110                let runtime_code = turbopack_ecmascript_runtime::get_nodejs_runtime_code(
111                    asset_context,
112                    RuntimeType::Development,
113                    this.include_async_module_runtime,
114                    generate_source_map,
115                );
116                code.push_code(&*runtime_code.await?);
117            }
118            RuntimeType::Production => {
119                let runtime_code = turbopack_ecmascript_runtime::get_nodejs_runtime_code(
120                    asset_context,
121                    RuntimeType::Production,
122                    this.include_async_module_runtime,
123                    generate_source_map,
124                );
125                code.push_code(&*runtime_code.await?);
126            }
127            #[cfg(feature = "test")]
128            RuntimeType::Dummy => {
129                let runtime_code = turbopack_ecmascript_runtime::get_dummy_runtime_code();
130                code.push_code(&runtime_code);
131            }
132        }
133
134        Ok(Code::cell(code.build()))
135    }
136
137    #[turbo_tasks::function]
138    async fn ident_for_path(self: Vc<Self>) -> Result<Vc<AssetIdent>> {
139        Ok(AssetIdent::from_path(
140            turbopack_ecmascript_runtime::embed_fs()
141                .root()
142                .await?
143                .join("runtime.js")?,
144        )
145        .into_vc())
146    }
147
148    #[turbo_tasks::function]
149    async fn source_map(self: Vc<Self>) -> Result<Vc<SourceMapAsset>> {
150        let this = self.await?;
151        Ok(SourceMapAsset::new(
152            Vc::upcast(*this.chunking_context),
153            self.ident_for_path(),
154            Vc::upcast(self),
155        ))
156    }
157}
158
159#[turbo_tasks::value_impl]
160impl OutputAssetsReference for EcmascriptBuildNodeRuntimeChunk {
161    #[turbo_tasks::function]
162    async fn references(self: Vc<Self>) -> Result<Vc<OutputAssetsWithReferenced>> {
163        let this = self.await?;
164        let mut references = vec![];
165
166        if *this
167            .chunking_context
168            .reference_chunk_source_maps(Vc::upcast(self))
169            .await?
170        {
171            references.push(ResolvedVc::upcast(self.source_map().to_resolved().await?))
172        }
173
174        Ok(OutputAssetsWithReferenced::from_assets(Vc::cell(
175            references,
176        )))
177    }
178}
179
180#[turbo_tasks::value_impl]
181impl OutputAsset for EcmascriptBuildNodeRuntimeChunk {
182    #[turbo_tasks::function]
183    async fn path(self: Vc<Self>) -> Result<Vc<FileSystemPath>> {
184        let this = self.await?;
185        let ident = self.ident_for_path();
186
187        Ok(this
188            .chunking_context
189            .chunk_path(Some(Vc::upcast(self)), ident, None, rcstr!(".js")))
190    }
191}
192
193#[turbo_tasks::value_impl]
194impl Asset for EcmascriptBuildNodeRuntimeChunk {
195    #[turbo_tasks::function]
196    async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
197        Ok(AssetContent::file(
198            FileContent::Content(File::from(
199                self.code()
200                    .to_rope_with_magic_comments(|| self.source_map())
201                    .await?,
202            ))
203            .cell(),
204        ))
205    }
206}
207
208#[turbo_tasks::value_impl]
209impl GenerateSourceMap for EcmascriptBuildNodeRuntimeChunk {
210    #[turbo_tasks::function]
211    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent> {
212        self.code().generate_source_map()
213    }
214}