turbopack_core/
environment.rs

1use std::{
2    process::{Command, Stdio},
3    str::FromStr,
4};
5
6use anyhow::{Context, Result, anyhow};
7use swc_core::ecma::preset_env::{Version, Versions};
8use turbo_rcstr::RcStr;
9use turbo_tasks::{ResolvedVc, TaskInput, Value, Vc};
10use turbo_tasks_env::ProcessEnv;
11
12use crate::target::CompileTarget;
13
14static DEFAULT_NODEJS_VERSION: &str = "16.0.0";
15
16#[turbo_tasks::value]
17#[derive(Clone, Copy, Default, Hash, TaskInput, Debug)]
18pub enum Rendering {
19    #[default]
20    None,
21    Client,
22    Server,
23}
24
25impl Rendering {
26    pub fn is_none(&self) -> bool {
27        matches!(self, Rendering::None)
28    }
29}
30
31#[turbo_tasks::value]
32pub enum ChunkLoading {
33    Edge,
34    /// CommonJS in Node.js
35    NodeJs,
36    /// <script> and <link> tags in the browser
37    Dom,
38}
39
40#[turbo_tasks::value]
41pub struct Environment {
42    // members must be private to avoid leaking non-custom types
43    execution: ExecutionEnvironment,
44}
45
46#[turbo_tasks::value_impl]
47impl Environment {
48    #[turbo_tasks::function]
49    pub fn new(execution: Value<ExecutionEnvironment>) -> Vc<Self> {
50        Self::cell(Environment {
51            execution: execution.into_value(),
52        })
53    }
54}
55
56#[turbo_tasks::value(serialization = "auto_for_input")]
57#[derive(Debug, Hash, Clone, Copy)]
58pub enum ExecutionEnvironment {
59    NodeJsBuildTime(ResolvedVc<NodeJsEnvironment>),
60    NodeJsLambda(ResolvedVc<NodeJsEnvironment>),
61    EdgeWorker(ResolvedVc<EdgeWorkerEnvironment>),
62    Browser(ResolvedVc<BrowserEnvironment>),
63    // TODO allow custom trait here
64    Custom(u8),
65}
66
67#[turbo_tasks::value_impl]
68impl Environment {
69    #[turbo_tasks::function]
70    pub async fn compile_target(&self) -> Result<Vc<CompileTarget>> {
71        Ok(match self.execution {
72            ExecutionEnvironment::NodeJsBuildTime(node_env, ..)
73            | ExecutionEnvironment::NodeJsLambda(node_env) => *node_env.await?.compile_target,
74            ExecutionEnvironment::Browser(_) => CompileTarget::unknown(),
75            ExecutionEnvironment::EdgeWorker(_) => CompileTarget::unknown(),
76            ExecutionEnvironment::Custom(_) => todo!(),
77        })
78    }
79
80    #[turbo_tasks::function]
81    pub async fn runtime_versions(&self) -> Result<Vc<RuntimeVersions>> {
82        Ok(match self.execution {
83            ExecutionEnvironment::NodeJsBuildTime(node_env, ..)
84            | ExecutionEnvironment::NodeJsLambda(node_env) => node_env.runtime_versions(),
85            ExecutionEnvironment::Browser(browser_env) => {
86                Vc::cell(Versions::parse_versions(browserslist::resolve(
87                    browser_env.await?.browserslist_query.split(','),
88                    &browserslist::Opts::default(),
89                )?)?)
90            }
91            ExecutionEnvironment::EdgeWorker(_) => todo!(),
92            ExecutionEnvironment::Custom(_) => todo!(),
93        })
94    }
95
96    #[turbo_tasks::function]
97    pub fn node_externals(&self) -> Vc<bool> {
98        match self.execution {
99            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
100                Vc::cell(true)
101            }
102            ExecutionEnvironment::Browser(_) => Vc::cell(false),
103            ExecutionEnvironment::EdgeWorker(_) => Vc::cell(false),
104            ExecutionEnvironment::Custom(_) => todo!(),
105        }
106    }
107
108    #[turbo_tasks::function]
109    pub fn supports_esm_externals(&self) -> Vc<bool> {
110        match self.execution {
111            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
112                Vc::cell(true)
113            }
114            ExecutionEnvironment::Browser(_) => Vc::cell(false),
115            ExecutionEnvironment::EdgeWorker(_) => Vc::cell(false),
116            ExecutionEnvironment::Custom(_) => todo!(),
117        }
118    }
119
120    #[turbo_tasks::function]
121    pub fn supports_commonjs_externals(&self) -> Vc<bool> {
122        match self.execution {
123            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
124                Vc::cell(true)
125            }
126            ExecutionEnvironment::Browser(_) => Vc::cell(false),
127            ExecutionEnvironment::EdgeWorker(_) => Vc::cell(true),
128            ExecutionEnvironment::Custom(_) => todo!(),
129        }
130    }
131
132    #[turbo_tasks::function]
133    pub fn supports_wasm(&self) -> Vc<bool> {
134        match self.execution {
135            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
136                Vc::cell(true)
137            }
138            ExecutionEnvironment::Browser(_) => Vc::cell(false),
139            ExecutionEnvironment::EdgeWorker(_) => Vc::cell(false),
140            ExecutionEnvironment::Custom(_) => todo!(),
141        }
142    }
143
144    #[turbo_tasks::function]
145    pub fn resolve_extensions(&self) -> Vc<Vec<RcStr>> {
146        let env = self;
147        match env.execution {
148            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
149                Vc::cell(vec![".js".into(), ".node".into(), ".json".into()])
150            }
151            ExecutionEnvironment::EdgeWorker(_) | ExecutionEnvironment::Browser(_) => {
152                Vc::<Vec<RcStr>>::default()
153            }
154            ExecutionEnvironment::Custom(_) => todo!(),
155        }
156    }
157
158    #[turbo_tasks::function]
159    pub fn resolve_node_modules(&self) -> Vc<bool> {
160        let env = self;
161        match env.execution {
162            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
163                Vc::cell(true)
164            }
165            ExecutionEnvironment::EdgeWorker(_) | ExecutionEnvironment::Browser(_) => {
166                Vc::cell(false)
167            }
168            ExecutionEnvironment::Custom(_) => todo!(),
169        }
170    }
171
172    #[turbo_tasks::function]
173    pub fn resolve_conditions(&self) -> Vc<Vec<RcStr>> {
174        let env = self;
175        match env.execution {
176            ExecutionEnvironment::NodeJsBuildTime(..) | ExecutionEnvironment::NodeJsLambda(_) => {
177                Vc::cell(vec!["node".into()])
178            }
179            ExecutionEnvironment::Browser(_) => Vc::<Vec<RcStr>>::default(),
180            ExecutionEnvironment::EdgeWorker(_) => {
181                Vc::cell(vec!["edge-light".into(), "worker".into()])
182            }
183            ExecutionEnvironment::Custom(_) => todo!(),
184        }
185    }
186
187    #[turbo_tasks::function]
188    pub async fn cwd(&self) -> Result<Vc<Option<RcStr>>> {
189        let env = self;
190        Ok(match env.execution {
191            ExecutionEnvironment::NodeJsBuildTime(env)
192            | ExecutionEnvironment::NodeJsLambda(env) => *env.await?.cwd,
193            _ => Vc::cell(None),
194        })
195    }
196
197    #[turbo_tasks::function]
198    pub fn rendering(&self) -> Vc<Rendering> {
199        let env = self;
200        match env.execution {
201            ExecutionEnvironment::NodeJsBuildTime(_) | ExecutionEnvironment::NodeJsLambda(_) => {
202                Rendering::Server.cell()
203            }
204            ExecutionEnvironment::EdgeWorker(_) => Rendering::Server.cell(),
205            ExecutionEnvironment::Browser(_) => Rendering::Client.cell(),
206            _ => Rendering::None.cell(),
207        }
208    }
209
210    #[turbo_tasks::function]
211    pub fn chunk_loading(&self) -> Vc<ChunkLoading> {
212        let env = self;
213        match env.execution {
214            ExecutionEnvironment::NodeJsBuildTime(_) | ExecutionEnvironment::NodeJsLambda(_) => {
215                ChunkLoading::NodeJs.cell()
216            }
217            ExecutionEnvironment::EdgeWorker(_) => ChunkLoading::Edge.cell(),
218            ExecutionEnvironment::Browser(_) => ChunkLoading::Dom.cell(),
219            ExecutionEnvironment::Custom(_) => todo!(),
220        }
221    }
222}
223
224pub enum NodeEnvironmentType {
225    Server,
226}
227
228#[turbo_tasks::value(shared)]
229pub struct NodeJsEnvironment {
230    pub compile_target: ResolvedVc<CompileTarget>,
231    pub node_version: ResolvedVc<NodeJsVersion>,
232    // user specified process.cwd
233    pub cwd: ResolvedVc<Option<RcStr>>,
234}
235
236impl Default for NodeJsEnvironment {
237    fn default() -> Self {
238        NodeJsEnvironment {
239            compile_target: CompileTarget::current_raw().resolved_cell(),
240            node_version: NodeJsVersion::default().resolved_cell(),
241            cwd: ResolvedVc::cell(None),
242        }
243    }
244}
245
246#[turbo_tasks::value_impl]
247impl NodeJsEnvironment {
248    #[turbo_tasks::function]
249    pub async fn runtime_versions(&self) -> Result<Vc<RuntimeVersions>> {
250        let str = match *self.node_version.await? {
251            NodeJsVersion::Current(process_env) => get_current_nodejs_version(*process_env),
252            NodeJsVersion::Static(version) => *version,
253        }
254        .await?;
255
256        Ok(Vc::cell(Versions {
257            node: Some(
258                Version::from_str(&str).map_err(|_| anyhow!("Node.js version parse error"))?,
259            ),
260            ..Default::default()
261        }))
262    }
263
264    #[turbo_tasks::function]
265    pub async fn current(process_env: ResolvedVc<Box<dyn ProcessEnv>>) -> Result<Vc<Self>> {
266        Ok(Self::cell(NodeJsEnvironment {
267            compile_target: CompileTarget::current().to_resolved().await?,
268            node_version: NodeJsVersion::cell(NodeJsVersion::Current(process_env))
269                .to_resolved()
270                .await?,
271            cwd: ResolvedVc::cell(None),
272        }))
273    }
274}
275
276#[turbo_tasks::value(shared)]
277pub enum NodeJsVersion {
278    Current(ResolvedVc<Box<dyn ProcessEnv>>),
279    Static(ResolvedVc<RcStr>),
280}
281
282impl Default for NodeJsVersion {
283    fn default() -> Self {
284        NodeJsVersion::Static(ResolvedVc::cell(DEFAULT_NODEJS_VERSION.into()))
285    }
286}
287
288#[turbo_tasks::value(shared)]
289pub struct BrowserEnvironment {
290    pub dom: bool,
291    pub web_worker: bool,
292    pub service_worker: bool,
293    pub browserslist_query: RcStr,
294}
295
296#[turbo_tasks::value(shared)]
297pub struct EdgeWorkerEnvironment {}
298
299// TODO preset_env_base::Version implements Serialize/Deserialize incorrectly
300#[turbo_tasks::value(transparent, serialization = "none")]
301pub struct RuntimeVersions(#[turbo_tasks(trace_ignore)] pub Versions);
302
303#[turbo_tasks::function]
304pub async fn get_current_nodejs_version(env: Vc<Box<dyn ProcessEnv>>) -> Result<Vc<RcStr>> {
305    let path_read = env.read("PATH".into()).await?;
306    let path = path_read.as_ref().context("env must have PATH")?;
307    let mut cmd = Command::new("node");
308    cmd.arg("--version");
309    cmd.env_clear();
310    cmd.env("PATH", path);
311    cmd.stdin(Stdio::piped());
312    cmd.stdout(Stdio::piped());
313
314    Ok(Vc::cell(
315        String::from_utf8(cmd.output()?.stdout)?
316            .strip_prefix('v')
317            .context("Version must begin with v")?
318            .strip_suffix('\n')
319            .context("Version must end with \\n")?
320            .into(),
321    ))
322}