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 NodeJs,
36 Dom,
38}
39
40#[turbo_tasks::value]
41pub struct Environment {
42 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 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 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#[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}