Skip to content

Commit

Permalink
React Server Components (#10043)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Dec 22, 2024
1 parent d19b1f6 commit ee5f98a
Show file tree
Hide file tree
Showing 82 changed files with 3,027 additions and 471 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

140 changes: 78 additions & 62 deletions crates/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![deny(unused_crate_dependencies)]

use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
Expand Down Expand Up @@ -44,9 +45,8 @@ pub type MacroCallback =
pub struct Macros<'a> {
/// Mapping of imported identifiers to import metadata.
macros: HashMap<Id, MacroImport>,
constants: HashMap<Id, Result<JsValue, Span>>,
evaluator: Evaluator<'a>,
callback: MacroCallback,
source_map: &'a SourceMap,
errors: &'a mut Vec<MacroError>,
load_errors: HashSet<String>,
assignment_span: Option<Span>,
Expand All @@ -70,10 +70,9 @@ impl<'a> Macros<'a> {
) -> Self {
Macros {
macros: HashMap::new(),
constants: HashMap::new(),
evaluator: Evaluator::new(source_map),
load_errors: HashSet::new(),
callback,
source_map,
errors,
assignment_span: None,
in_call: false,
Expand Down Expand Up @@ -137,7 +136,7 @@ impl<'a> Macros<'a> {
// Try to statically evaluate all of the function arguments.
let mut args = Vec::with_capacity(call.args.len());
for arg in &call.args {
match self.eval(&*arg.expr) {
match self.evaluator.eval(&*arg.expr) {
Ok(val) => {
if arg.spread.is_none() {
args.push(val);
Expand All @@ -154,13 +153,13 @@ impl<'a> Macros<'a> {
}

// If that was successful, call the function callback (on the JS thread).
let loc = self.source_map.lookup_char_pos(call.span.lo);
let loc = self.evaluator.source_map.lookup_char_pos(call.span.lo);
let loc = Location {
line: loc.line as u32,
col: loc.col_display as u32,
};
match (self.callback)(src.clone(), export, args, loc) {
Ok(val) => Ok(self.value_to_expr(val)?),
Ok(val) => Ok(self.evaluator.value_to_expr(val)?),
Err(err) => match err {
MacroError::LoadError(err, _) => {
self.load_errors.insert(src);
Expand Down Expand Up @@ -224,7 +223,7 @@ impl<'a> Fold for Macros<'a> {
let imported = match &member.prop {
MemberProp::Ident(id) => id.sym.to_string(),
MemberProp::Computed(s) => {
if let Ok(JsValue::String(s)) = self.eval(&s.expr) {
if let Ok(JsValue::String(s)) = self.evaluator.eval(&s.expr) {
s
} else {
break 'block;
Expand Down Expand Up @@ -263,8 +262,8 @@ impl<'a> Fold for Macros<'a> {
if node.kind == VarDeclKind::Const {
for decl in &node.decls {
if let Some(expr) = &decl.init {
let val = self.eval(&*expr);
self.eval_pat(val, &decl.name);
let val = self.evaluator.eval(&*expr);
self.evaluator.eval_pat(val, &decl.name);
}
}
}
Expand All @@ -286,7 +285,7 @@ impl<'a> Fold for Macros<'a> {
// Error when re-assigning a property of a constant that's used in a macro.
let node = node.fold_children_with(self);
if let Expr::Ident(id) = &*node.obj {
if let Some(constant) = self.constants.get_mut(&id.to_id()) {
if let Some(constant) = self.evaluator.constants.get_mut(&id.to_id()) {
if constant.is_ok() {
*constant = Err(assignment_span.clone());
}
Expand All @@ -299,8 +298,9 @@ impl<'a> Fold for Macros<'a> {
// If the member expression evaluates to an object, continue traversing so we error in fold_ident.
// Otherwise, return early to allow other properties to be accessed without error.
let value = self
.evaluator
.eval(&*node.obj)
.and_then(|obj| self.eval_member_prop(obj, &node));
.and_then(|obj| self.evaluator.eval_member_prop(obj, &node));
if !matches!(
value,
Err(..) | Ok(JsValue::Object(..) | JsValue::Array(..))
Expand All @@ -314,7 +314,7 @@ impl<'a> Fold for Macros<'a> {

fn fold_ident(&mut self, node: Ident) -> Ident {
if self.in_call {
if let Some(constant) = self.constants.get_mut(&node.to_id()) {
if let Some(constant) = self.evaluator.constants.get_mut(&node.to_id()) {
if matches!(constant, Ok(JsValue::Object(..) | JsValue::Array(..))) {
// Mark access to constant object inside a call as an error since it could potentially be mutated.
*constant = Err(node.span.clone());
Expand Down Expand Up @@ -362,7 +362,8 @@ fn handle_error(result: Result<Expr, MacroError>, errors: &mut Vec<MacroError>)
}

/// A type that represents a basic JS value.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JsValue {
Undefined,
Null,
Expand All @@ -375,9 +376,21 @@ pub enum JsValue {
Function(String),
}

impl<'a> Macros<'a> {
pub struct Evaluator<'a> {
constants: HashMap<Id, Result<JsValue, Span>>,
source_map: &'a SourceMap,
}

impl<'a> Evaluator<'a> {
pub fn new(source_map: &'a SourceMap) -> Evaluator<'_> {
Evaluator {
constants: HashMap::new(),
source_map,
}
}

/// Statically evaluate a JS expression to a value, if possible.
fn eval(&self, expr: &Expr) -> Result<JsValue, Span> {
pub fn eval(&self, expr: &Expr) -> Result<JsValue, Span> {
match expr.unwrap_parens() {
Expr::Lit(lit) => match lit {
Lit::Null(_) => Ok(JsValue::Null),
Expand Down Expand Up @@ -437,49 +450,7 @@ impl<'a> Macros<'a> {
}
Ok(JsValue::Array(res))
}
Expr::Object(obj) => {
let mut res = IndexMap::with_capacity(obj.props.len());
for prop in &obj.props {
match prop {
PropOrSpread::Prop(prop) => match &**prop {
Prop::KeyValue(kv) => {
let v = self.eval(&*kv.value)?;
let k = match &kv.key {
PropName::Ident(IdentName { sym, .. })
| PropName::Str(Str { value: sym, .. }) => sym.to_string(),
PropName::Num(n) => n.value.to_string(),
PropName::Computed(c) => match self.eval(&*c.expr) {
Err(e) => return Err(e),
Ok(JsValue::String(s)) => s,
Ok(JsValue::Number(n)) => n.to_string(),
Ok(JsValue::Bool(b)) => b.to_string(),
_ => return Err(c.span),
},
PropName::BigInt(v) => return Err(v.span),
};

res.insert(k.to_string(), v);
}
Prop::Shorthand(s) => {
if let Some(val) = self.constants.get(&s.to_id()) {
res.insert(s.sym.to_string(), val.clone()?);
} else {
return Err(s.span);
}
}
_ => return Err(obj.span),
},
PropOrSpread::Spread(spread) => {
let v = self.eval(&*spread.expr)?;
match v {
JsValue::Object(o) => res.extend(o),
_ => return Err(obj.span),
}
}
}
}
Ok(JsValue::Object(res))
}
Expr::Object(obj) => self.eval_object(obj),
Expr::Bin(bin) => match (bin.op, self.eval(&*bin.left), self.eval(&*bin.right)) {
(BinaryOp::Add, Ok(JsValue::String(a)), Ok(JsValue::String(b))) => {
Ok(JsValue::String(format!("{}{}", a, b)))
Expand Down Expand Up @@ -676,7 +647,52 @@ impl<'a> Macros<'a> {
}
}

fn eval_member_prop(&self, obj: JsValue, member: &MemberExpr) -> Result<JsValue, Span> {
pub fn eval_object(&self, obj: &ObjectLit) -> Result<JsValue, Span> {
let mut res = IndexMap::with_capacity(obj.props.len());
for prop in &obj.props {
match prop {
PropOrSpread::Prop(prop) => match &**prop {
Prop::KeyValue(kv) => {
let v = self.eval(&*kv.value)?;
let k = match &kv.key {
PropName::Ident(IdentName { sym, .. }) | PropName::Str(Str { value: sym, .. }) => {
sym.to_string()
}
PropName::Num(n) => n.value.to_string(),
PropName::Computed(c) => match self.eval(&*c.expr) {
Err(e) => return Err(e),
Ok(JsValue::String(s)) => s,
Ok(JsValue::Number(n)) => n.to_string(),
Ok(JsValue::Bool(b)) => b.to_string(),
_ => return Err(c.span),
},
PropName::BigInt(v) => return Err(v.span),
};

res.insert(k.to_string(), v);
}
Prop::Shorthand(s) => {
if let Some(val) = self.constants.get(&s.to_id()) {
res.insert(s.sym.to_string(), val.clone()?);
} else {
return Err(s.span);
}
}
_ => return Err(obj.span),
},
PropOrSpread::Spread(spread) => {
let v = self.eval(&*spread.expr)?;
match v {
JsValue::Object(o) => res.extend(o),
_ => return Err(obj.span),
}
}
}
}
Ok(JsValue::Object(res))
}

pub fn eval_member_prop(&self, obj: JsValue, member: &MemberExpr) -> Result<JsValue, Span> {
match &member.prop {
MemberProp::Ident(id) => obj.get_id(id.as_ref()).ok_or(member.span),
MemberProp::Computed(prop) => {
Expand All @@ -688,7 +704,7 @@ impl<'a> Macros<'a> {
}

/// Convert JS value to AST.
fn value_to_expr(&self, value: JsValue) -> Result<Expr, MacroError> {
pub fn value_to_expr(&self, value: JsValue) -> Result<Expr, MacroError> {
Ok(match value {
JsValue::Null => Expr::Lit(Lit::Null(Null::dummy())),
JsValue::Undefined => Expr::Ident(Ident::new_no_ctxt(js_word!("undefined"), DUMMY_SP)),
Expand Down Expand Up @@ -764,7 +780,7 @@ impl<'a> Macros<'a> {
})
}

fn eval_pat(&mut self, value: Result<JsValue, Span>, pat: &Pat) {
pub fn eval_pat(&mut self, value: Result<JsValue, Span>, pat: &Pat) {
match pat {
Pat::Ident(name) => {
self.constants.insert(name.to_id(), value);
Expand Down
2 changes: 1 addition & 1 deletion crates/node-bindings/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub struct JsFileSystemOptions {
pub struct JsResolverOptions {
pub fs: Option<JsFileSystemOptions>,
pub include_node_modules: Option<NapiSideEffectsVariants>,
pub conditions: Option<u16>,
pub conditions: Option<u32>,
pub module_dir_resolver: Option<JsFunction>,
pub mode: u8,
pub entries: Option<u8>,
Expand Down
Loading

0 comments on commit ee5f98a

Please sign in to comment.