Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Server Components #10043

Merged
merged 41 commits into from
Dec 22, 2024
Merged
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
22e4dbe
Evaluate import attributes
devongovett Dec 1, 2024
3fc1a7f
Add react-client and react-server environments
devongovett Dec 1, 2024
06a5a4f
Support "source" exports condition
devongovett Dec 1, 2024
4b7348c
Handle "use client" and "use server" directives
devongovett Dec 1, 2024
acec228
Support "use server-entry" to create isolated bundle groups
devongovett Dec 1, 2024
8cf1718
HMR
devongovett Dec 1, 2024
67353f9
Fix scope hoisted builds
devongovett Dec 2, 2024
fc7110b
Add import.meta.distDir and import.meta.publicUrl
devongovett Dec 2, 2024
a0b8bb4
RSC runtime
devongovett Dec 6, 2024
33eab53
RSC example
devongovett Dec 6, 2024
51a99fa
Register server references in client bundles correctly
devongovett Dec 8, 2024
69ab107
Don't execute module in HMR that haven't already loaded
devongovett Dec 8, 2024
c8cd3a9
Fix dependency collector bug
devongovett Dec 8, 2024
b8b44b6
Ignore of ws module is not available in HMR runtime
devongovett Dec 8, 2024
8aac352
Fix bundle graph check for async bundles
devongovett Dec 8, 2024
a160634
Fix library bundler for bundle graph change
devongovett Dec 9, 2024
86d5195
Handle internalized async dependencies
devongovett Dec 9, 2024
4eb2f66
Don't remove type=module in development to avoid multiple copies of a…
devongovett Dec 9, 2024
9ea9318
prettier
devongovett Dec 9, 2024
a256737
Resolve react relative to app
devongovett Dec 9, 2024
24f9c46
Use react-server-dom-parcel from npm
devongovett Dec 9, 2024
61f02c2
Fix default node engines in test
devongovett Dec 9, 2024
561beac
Allow parallel bundles with different contexts from the root bundle b…
devongovett Dec 10, 2024
d9b984f
Don't transpile dynamic imports in TS plugins
devongovett Dec 10, 2024
215dd5e
Remove error that "Only browser targets are supported in serve mode"
devongovett Dec 10, 2024
0fa0008
Use parcelRequire.load to load bundles
devongovett Dec 10, 2024
c0b0ee6
lint
devongovett Dec 10, 2024
e0a86c2
fix test
devongovett Dec 10, 2024
b1c3ad4
Bump react-server-dom-parcel
devongovett Dec 10, 2024
0b82b13
format
devongovett Dec 10, 2024
f12dac0
Bump react to 19
devongovett Dec 16, 2024
07f24a8
Replace typeof process with 'undefined' to avoid adding polyfill for …
devongovett Dec 16, 2024
1f7c98d
normalize publicUrl
devongovett Dec 16, 2024
1cb5ae5
treat context changes async bundles
devongovett Dec 16, 2024
6c9a184
refactor to let react insert scripts
devongovett Dec 16, 2024
9f297b1
add tests
devongovett Dec 16, 2024
6a13048
Improve inference for targets in serve mode
devongovett Dec 22, 2024
4e7c3a4
Bundle node_modules in the react-server context for now
devongovett Dec 22, 2024
4e0b155
fixes
devongovett Dec 22, 2024
93be6cf
Merge branch 'v2' of github.com:parcel-bundler/parcel into rsc2
devongovett Dec 22, 2024
1f5f005
update versions
devongovett Dec 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Evaluate import attributes
devongovett committed Dec 1, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
commit 22e4dbe1b0b1f2fb7c2a4c8f1acc1ae5c05cb1a9
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,
@@ -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>,
@@ -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,
@@ -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);
@@ -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);
@@ -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;
@@ -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);
}
}
}
@@ -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());
}
@@ -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(..))
@@ -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());
@@ -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,
@@ -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),
@@ -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)))
@@ -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) => {
@@ -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)),
@@ -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);
74 changes: 34 additions & 40 deletions packages/transformers/js/core/src/dependency_collector.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use std::{
collections::{hash_map::DefaultHasher, HashMap},
collections::hash_map::DefaultHasher,
fmt,
hash::{Hash, Hasher},
path::Path,
};

use parcel_macros::{Evaluator, JsValue};
use path_slash::PathBufExt;
use serde::{Deserialize, Serialize};
use swc_core::{
common::{sync::Lrc, Mark, SourceMap, Span, SyntaxContext, DUMMY_SP},
ecma::{
ast::{self, Callee, IdentName, MemberProp},
ast::{self, Callee, MemberProp},
atoms::{js_word, JsWord},
utils::{member_expr, stack_size::maybe_grow_default},
visit::{Fold, FoldWith},
@@ -103,13 +104,13 @@ impl fmt::Display for DependencyKind {
}
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DependencyDescriptor {
pub kind: DependencyKind,
pub loc: SourceLocation,
/// The text specifier associated with the import/export statement.
pub specifier: swc_core::ecma::atoms::JsWord,
pub attributes: Option<HashMap<swc_core::ecma::atoms::JsWord, bool>>,
pub attributes: Option<JsValue>,
pub is_optional: bool,
pub is_helper: bool,
pub source_type: Option<SourceType>,
@@ -158,7 +159,7 @@ impl<'a> DependencyCollector<'a> {
mut specifier: JsWord,
span: swc_core::common::Span,
kind: DependencyKind,
attributes: Option<HashMap<swc_core::ecma::atoms::JsWord, bool>>,
attributes: Option<JsValue>,
is_optional: bool,
source_type: SourceType,
) -> Option<JsWord> {
@@ -344,11 +345,17 @@ impl<'a> Fold for DependencyCollector<'a> {
return node;
}

let evaluator = Evaluator::new(&self.source_map);
let attributes = node
.with
.as_ref()
.and_then(|attrs| evaluator.eval_object(&*attrs).ok());

let rewritten = self.add_dependency(
node.src.value.clone(),
node.src.span,
DependencyKind::Import,
None,
attributes,
false,
self.config.source_type,
);
@@ -366,11 +373,17 @@ impl<'a> Fold for DependencyCollector<'a> {
return node;
}

let evaluator = Evaluator::new(&self.source_map);
let attributes = node
.with
.as_ref()
.and_then(|attrs| evaluator.eval_object(&*attrs).ok());

let rewritten = self.add_dependency(
src.value.clone(),
src.span,
DependencyKind::Export,
None,
attributes,
false,
self.config.source_type,
);
@@ -384,11 +397,17 @@ impl<'a> Fold for DependencyCollector<'a> {
}

fn fold_export_all(&mut self, mut node: ast::ExportAll) -> ast::ExportAll {
let evaluator = Evaluator::new(&self.source_map);
let attributes = node
.with
.as_ref()
.and_then(|attrs| evaluator.eval_object(&*attrs).ok());

let rewritten = self.add_dependency(
node.src.value.clone(),
node.src.span,
DependencyKind::Export,
None,
attributes,
false,
self.config.source_type,
);
@@ -637,38 +656,14 @@ impl<'a> Fold for DependencyCollector<'a> {
let mut attributes = None;
if kind == DependencyKind::DynamicImport {
if let Some(arg) = node.args.get(1) {
if let Object(arg) = &*arg.expr {
let mut attrs = HashMap::new();
for key in &arg.props {
let prop = match key {
ast::PropOrSpread::Prop(prop) => prop,
_ => continue,
};

let kv = match &**prop {
ast::Prop::KeyValue(kv) => kv,
_ => continue,
};

let k = match &kv.key {
ast::PropName::Ident(IdentName { sym, .. })
| ast::PropName::Str(ast::Str { value: sym, .. }) => sym.clone(),
_ => continue,
};

let v = match &*kv.value {
Lit(ast::Lit::Bool(ast::Bool { value, .. })) => *value,
_ => continue,
};

attrs.insert(k, v);
}

attributes = Some(attrs);
if let Object(_) = &*arg.expr {
let evaluator = Evaluator::new(&self.source_map);
attributes = evaluator.eval(&*arg.expr).ok();
}
}
}

let mut is_specifier_str = false;
let node = if let Some(arg) = node.args.first() {
if kind == DependencyKind::ServiceWorker || kind == DependencyKind::Worklet {
let (source_type, opts) = if kind == DependencyKind::ServiceWorker {
@@ -726,6 +721,7 @@ impl<'a> Fold for DependencyCollector<'a> {
}

if let Some((specifier, span)) = match_str(&arg.expr) {
is_specifier_str = true;
// require() calls aren't allowed in scripts, flag as an error.
if kind == DependencyKind::Require && self.config.source_type == SourceType::Script {
self.add_script_error(node.span);
@@ -762,7 +758,7 @@ impl<'a> Fold for DependencyCollector<'a> {
// Replace import() with require()
if kind == DependencyKind::DynamicImport {
let mut call = node;
if !self.config.scope_hoist && !self.config.standalone {
if !self.config.scope_hoist && !self.config.standalone && is_specifier_str {
let name = match &self.config.source_type {
SourceType::Module => "require",
SourceType::Script => "__parcel__require__",
@@ -1536,9 +1532,7 @@ mod test {
}

fn make_config() -> Config {
let mut config = Config::default();
config.is_browser = true;
config
Config::default()
}

fn make_placeholder_hash(specifier: &str, dependency_kind: DependencyKind) -> String {