Skip to content

Commit 2467728

Browse files
committed
feat(turbopack): support full json types and basical evaluation of compile time define env (#28)
* feat(turbopack): support more types of compile time define env * fix: should use Syntax::Es for evaluate define env parsing
1 parent 9b46ef7 commit 2467728

File tree

12 files changed

+246
-50
lines changed

12 files changed

+246
-50
lines changed

crates/next-core/src/next_client/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::iter::once;
1+
use std::{iter::once, str::FromStr};
22

33
use anyhow::Result;
44
use serde::{Deserialize, Serialize};

crates/next-core/src/next_server/context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::iter::once;
1+
use std::{iter::once, str::FromStr};
22

33
use anyhow::{Result, bail};
44
use serde::{Deserialize, Serialize};

turbopack/crates/turbo-tasks/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,20 @@ futures = { workspace = true }
3333
indexmap = { workspace = true, features = ["serde"] }
3434
mopa = "0.2.0"
3535
once_cell = { workspace = true }
36-
parking_lot = { workspace = true, features = ["serde"]}
36+
parking_lot = { workspace = true, features = ["serde"] }
3737
pin-project-lite = { workspace = true }
3838
rayon = { workspace = true }
3939
regex = { workspace = true }
4040
rustc-hash = { workspace = true }
4141
serde = { workspace = true, features = ["rc", "derive"] }
4242
serde_json = { workspace = true }
4343
serde_regex = "1.1.0"
44-
shrink-to-fit = { workspace=true,features = ["indexmap", "serde_json", "smallvec", "nightly"] }
44+
shrink-to-fit = { workspace = true, features = [
45+
"indexmap",
46+
"serde_json",
47+
"smallvec",
48+
"nightly",
49+
] }
4550
smallvec = { workspace = true }
4651
thiserror = { workspace = true }
4752
tokio = { workspace = true, features = ["full"] }

turbopack/crates/turbo-tasks/src/task/task_input.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ where
6767
async fn resolve_input(&self) -> Result<Self> {
6868
let mut resolved = Vec::with_capacity(self.len());
6969
for value in self {
70-
resolved.push(value.resolve_input().await?);
70+
resolved.push(Box::pin(value.resolve_input()).await?);
7171
}
7272
Ok(resolved)
7373
}

turbopack/crates/turbopack-core/src/compile_time_info.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,14 @@ macro_rules! free_var_references {
104104
#[turbo_tasks::value]
105105
#[derive(Debug, Clone, Hash, TaskInput)]
106106
pub enum CompileTimeDefineValue {
107+
Null,
107108
Bool(bool),
109+
Number(RcStr),
108110
String(RcStr),
109-
JSON(RcStr),
111+
Array(Vec<CompileTimeDefineValue>),
112+
Object(Vec<(RcStr, CompileTimeDefineValue)>),
110113
Undefined,
114+
Evaluate(RcStr),
111115
}
112116

113117
impl From<bool> for CompileTimeDefineValue {
@@ -136,7 +140,16 @@ impl From<&str> for CompileTimeDefineValue {
136140

137141
impl From<serde_json::Value> for CompileTimeDefineValue {
138142
fn from(value: serde_json::Value) -> Self {
139-
Self::JSON(value.to_string().into())
143+
match value {
144+
serde_json::Value::Null => Self::Null,
145+
serde_json::Value::Bool(b) => Self::Bool(b),
146+
serde_json::Value::Number(n) => Self::Number(n.to_string().into()),
147+
serde_json::Value::String(s) => Self::String(s.into()),
148+
serde_json::Value::Array(a) => Self::Array(a.into_iter().map(|i| i.into()).collect()),
149+
serde_json::Value::Object(m) => {
150+
Self::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
151+
}
152+
}
140153
}
141154
}
142155

turbopack/crates/turbopack-ecmascript/src/analyzer/mod.rs

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ pub enum ConstantValue {
185185
Null,
186186
BigInt(Box<BigInt>),
187187
Regex(Box<(Atom, Atom)>),
188+
Evaluate(RcStr),
188189
}
189190

190191
impl ConstantValue {
@@ -203,25 +204,27 @@ impl ConstantValue {
203204
}
204205
}
205206

206-
pub fn is_truthy(&self) -> bool {
207+
pub fn is_truthy(&self) -> Option<bool> {
207208
match self {
208-
Self::Undefined | Self::False | Self::Null => false,
209-
Self::True | Self::Regex(..) => true,
210-
Self::Str(s) => !s.is_empty(),
211-
Self::Num(ConstantNumber(n)) => *n != 0.0,
212-
Self::BigInt(n) => !n.is_zero(),
209+
Self::Undefined | Self::False | Self::Null => Some(false),
210+
Self::True | Self::Regex(..) => Some(true),
211+
Self::Str(s) => Some(!s.is_empty()),
212+
Self::Num(ConstantNumber(n)) => Some(*n != 0.0),
213+
Self::BigInt(n) => Some(!n.is_zero()),
214+
Self::Evaluate(_) => None,
213215
}
214216
}
215217

216-
pub fn is_nullish(&self) -> bool {
218+
pub fn is_nullish(&self) -> Option<bool> {
217219
match self {
218-
Self::Undefined | Self::Null => true,
220+
Self::Undefined | Self::Null => Some(true),
219221
Self::Str(..)
220222
| Self::Num(..)
221223
| Self::True
222224
| Self::False
223225
| Self::BigInt(..)
224-
| Self::Regex(..) => false,
226+
| Self::Regex(..) => Some(false),
227+
Self::Evaluate(_) => None,
225228
}
226229
}
227230

@@ -233,7 +236,16 @@ impl ConstantValue {
233236
}
234237

235238
pub fn is_value_type(&self) -> bool {
236-
!matches!(self, Self::Regex(..))
239+
match self {
240+
ConstantValue::Undefined
241+
| ConstantValue::Null
242+
| ConstantValue::Str(_)
243+
| ConstantValue::Num(_)
244+
| ConstantValue::True
245+
| ConstantValue::False
246+
| ConstantValue::BigInt(_) => true,
247+
ConstantValue::Regex(_) | ConstantValue::Evaluate(_) => false,
248+
}
237249
}
238250
}
239251

@@ -283,6 +295,7 @@ impl Display for ConstantValue {
283295
ConstantValue::Num(ConstantNumber(n)) => write!(f, "{n}"),
284296
ConstantValue::BigInt(n) => write!(f, "{n}"),
285297
ConstantValue::Regex(regex) => write!(f, "/{}/{}", regex.0, regex.1),
298+
ConstantValue::Evaluate(eval) => write!(f, "{eval}"),
286299
}
287300
}
288301
}
@@ -584,12 +597,37 @@ impl From<ConstantValue> for JsValue {
584597
impl From<&CompileTimeDefineValue> for JsValue {
585598
fn from(v: &CompileTimeDefineValue) -> Self {
586599
match v {
587-
CompileTimeDefineValue::String(s) => JsValue::Constant(s.as_str().into()),
600+
CompileTimeDefineValue::Null => JsValue::Constant(ConstantValue::Null),
588601
CompileTimeDefineValue::Bool(b) => JsValue::Constant((*b).into()),
589-
CompileTimeDefineValue::JSON(_) => {
590-
JsValue::unknown_empty(false, "compile time injected JSON")
602+
CompileTimeDefineValue::Number(n) => JsValue::Constant(ConstantValue::Num(
603+
ConstantNumber(n.as_str().parse::<f64>().unwrap()),
604+
)),
605+
CompileTimeDefineValue::String(s) => JsValue::Constant(s.as_str().into()),
606+
CompileTimeDefineValue::Array(a) => {
607+
let mut js_value = JsValue::Array {
608+
total_nodes: a.len() as u32,
609+
items: a.iter().map(|i| i.into()).collect(),
610+
mutable: false,
611+
};
612+
js_value.update_total_nodes();
613+
js_value
614+
}
615+
CompileTimeDefineValue::Object(m) => {
616+
let mut js_value = JsValue::Object {
617+
total_nodes: m.len() as u32,
618+
parts: m
619+
.iter()
620+
.map(|(k, v)| ObjectPart::KeyValue(k.clone().into(), v.into()))
621+
.collect(),
622+
mutable: false,
623+
};
624+
js_value.update_total_nodes();
625+
js_value
591626
}
592627
CompileTimeDefineValue::Undefined => JsValue::Constant(ConstantValue::Undefined),
628+
CompileTimeDefineValue::Evaluate(s) => {
629+
JsValue::Constant(ConstantValue::Evaluate(s.clone()))
630+
}
593631
}
594632
}
595633
}
@@ -2192,7 +2230,7 @@ impl JsValue {
21922230
/// Some if we know if or if not the value is truthy.
21932231
pub fn is_truthy(&self) -> Option<bool> {
21942232
match self {
2195-
JsValue::Constant(c) => Some(c.is_truthy()),
2233+
JsValue::Constant(c) => c.is_truthy(),
21962234
JsValue::Concat(..) => self.is_empty_string().map(|x| !x),
21972235
JsValue::Url(..)
21982236
| JsValue::Array { .. }
@@ -2273,7 +2311,7 @@ impl JsValue {
22732311
/// don't know. Returns Some if we know if or if not the value is nullish.
22742312
pub fn is_nullish(&self) -> Option<bool> {
22752313
match self {
2276-
JsValue::Constant(c) => Some(c.is_nullish()),
2314+
JsValue::Constant(c) => c.is_nullish(),
22772315
JsValue::Concat(..)
22782316
| JsValue::Url(..)
22792317
| JsValue::Array { .. }

turbopack/crates/turbopack-ecmascript/src/references/constant_value.rs

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
use std::{path::PathBuf, str::FromStr};
2+
13
use anyhow::Result;
24
use serde::{Deserialize, Serialize};
3-
use swc_core::quote;
5+
use swc_core::{
6+
common::{DUMMY_SP, SourceMap, sync::Lrc},
7+
ecma::{
8+
ast::{ArrayLit, EsVersion, Expr, KeyValueProp, ObjectLit, Prop, PropName, Str},
9+
parser::{Syntax, parse_file_as_expr},
10+
},
11+
quote,
12+
};
413
use turbo_tasks::{NonLocalValue, TaskInput, Vc, debug::ValueDebugFormat, trace::TraceRawVcs};
514
use turbopack_core::{chunk::ChunkingContext, compile_time_info::CompileTimeDefineValue};
615

@@ -39,24 +48,8 @@ impl ConstantValueCodeGen {
3948
let value = self.value.clone();
4049

4150
let visitor = create_visitor!(self.path, visit_mut_expr, |expr: &mut Expr| {
42-
*expr = match value {
43-
CompileTimeDefineValue::Bool(true) => {
44-
quote!("(\"TURBOPACK compile-time value\", true)" as Expr)
45-
}
46-
CompileTimeDefineValue::Bool(false) => {
47-
quote!("(\"TURBOPACK compile-time value\", false)" as Expr)
48-
}
49-
CompileTimeDefineValue::String(ref s) => {
50-
quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = s.to_string().into())
51-
}
52-
CompileTimeDefineValue::JSON(ref s) => {
53-
quote!("(\"TURBOPACK compile-time value\", JSON.parse($e))" as Expr, e: Expr = s.to_string().into())
54-
}
55-
// undefined can be re-bound, so use `void 0` to avoid any risks
56-
CompileTimeDefineValue::Undefined => {
57-
quote!("(\"TURBOPACK compile-time value\", void 0)" as Expr)
58-
}
59-
};
51+
// TODO: avoid this clone
52+
*expr = define_env_to_expr((value).clone());
6053
});
6154

6255
Ok(CodeGeneration::visitors(vec![visitor]))
@@ -68,3 +61,73 @@ impl From<ConstantValueCodeGen> for CodeGen {
6861
CodeGen::ConstantValueCodeGen(val)
6962
}
7063
}
64+
65+
fn define_env_to_expr(value: CompileTimeDefineValue) -> Expr {
66+
match value {
67+
CompileTimeDefineValue::Null => {
68+
quote!("(\"TURBOPACK compile-time value\", null)" as Expr)
69+
}
70+
CompileTimeDefineValue::Bool(true) => {
71+
quote!("(\"TURBOPACK compile-time value\", true)" as Expr)
72+
}
73+
CompileTimeDefineValue::Bool(false) => {
74+
quote!("(\"TURBOPACK compile-time value\", false)" as Expr)
75+
}
76+
CompileTimeDefineValue::Number(ref n) => {
77+
quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = n.parse::<f64>().unwrap().into())
78+
}
79+
CompileTimeDefineValue::String(ref s) => {
80+
quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = s.to_string().into())
81+
}
82+
CompileTimeDefineValue::Array(a) => {
83+
quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = Expr::Array(ArrayLit {
84+
span: DUMMY_SP,
85+
elems: a.into_iter().map(|i| Some(define_env_to_expr(i).into())).collect(),
86+
}))
87+
}
88+
CompileTimeDefineValue::Object(m) => {
89+
quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = Expr::Object(ObjectLit {
90+
span: DUMMY_SP,
91+
props: m
92+
.into_iter()
93+
.map(|(k, v)| {
94+
swc_core::ecma::ast::PropOrSpread::Prop(
95+
Prop::KeyValue(KeyValueProp {
96+
key: PropName::Str(Str::from(k.as_str())),
97+
value: define_env_to_expr(v).into(),
98+
})
99+
.into(),
100+
)
101+
})
102+
.collect(),
103+
}))
104+
}
105+
CompileTimeDefineValue::Undefined => {
106+
quote!("(\"TURBOPACK compile-time value\", void 0)" as Expr)
107+
}
108+
CompileTimeDefineValue::Evaluate(ref s) => parse_code_to_expr(s.to_string()),
109+
}
110+
}
111+
112+
fn parse_code_to_expr(code: String) -> Expr {
113+
let cm = Lrc::new(SourceMap::default());
114+
let fm = cm.new_source_file(
115+
Lrc::new(
116+
PathBuf::from_str("__compile_time_define_value_internal__.js")
117+
.unwrap()
118+
.into(),
119+
),
120+
code.clone(),
121+
);
122+
parse_file_as_expr(
123+
&fm,
124+
Syntax::Es(Default::default()),
125+
EsVersion::latest(),
126+
None,
127+
&mut vec![],
128+
)
129+
.map_or(
130+
quote!("$s" as Expr, s: Expr = code.into()),
131+
|expr| quote!("(\"TURBOPACK compile-time value\", $e)" as Expr, e: Expr = *expr),
132+
)
133+
}

turbopack/crates/turbopack-ecmascript/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub fn js_value_to_pattern(value: &JsValue) -> Pattern {
4747
ConstantValue::BigInt(n) => n.to_string().into(),
4848
ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
4949
ConstantValue::Undefined => rcstr!("undefined"),
50+
ConstantValue::Evaluate(eval) => eval.clone(),
5051
}),
5152
JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()),
5253
JsValue::Alternatives {

turbopack/crates/turbopack-tests/tests/snapshot.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use turbopack_core::{
3434
EvaluatableAssets, MinifyType, availability_info::AvailabilityInfo,
3535
},
3636
compile_time_defines,
37-
compile_time_info::CompileTimeInfo,
37+
compile_time_info::{CompileTimeDefineValue, CompileTimeInfo, DefineableNameSegment},
3838
condition::ContextCondition,
3939
context::AssetContext,
4040
environment::{BrowserEnvironment, Environment, ExecutionEnvironment, NodeJsEnvironment},
@@ -257,15 +257,33 @@ async fn run_test_operation(resource: RcStr) -> Result<Vc<FileSystemPath>> {
257257
.to_resolved()
258258
.await?;
259259

260-
let defines = compile_time_defines!(
260+
let mut defines = compile_time_defines!(
261261
process.turbopack = true,
262262
process.env.TURBOPACK = true,
263263
process.env.NODE_ENV = "development",
264264
DEFINED_VALUE = "value",
265265
DEFINED_TRUE = true,
266+
DEFINED_NULL = json!(null),
267+
DEFINED_INT = json!(1),
268+
DEFINED_FLOAT = json!(0.01),
269+
DEFINED_ARRAY = json!([ false, 0, "1", { "v": "v" }, null ]),
266270
A.VERY.LONG.DEFINED.VALUE = json!({ "test": true }),
267271
);
268272

273+
defines.0.insert(
274+
vec![DefineableNameSegment::from("DEFINED_EVALED")],
275+
CompileTimeDefineValue::Evaluate("1 + 1".into()),
276+
);
277+
278+
defines.0.insert(
279+
vec![DefineableNameSegment::from("DEFINED_EVALED_NESTED")],
280+
CompileTimeDefineValue::Array(vec![
281+
CompileTimeDefineValue::Bool(true),
282+
CompileTimeDefineValue::Undefined,
283+
CompileTimeDefineValue::Evaluate("() => 1".into()),
284+
]),
285+
);
286+
269287
let compile_time_info = CompileTimeInfo::builder(env)
270288
.defines(defines.clone().resolved_cell())
271289
.free_var_references(free_var_references!(..defines.into_iter()).resolved_cell())

0 commit comments

Comments
 (0)