Skip to content

Commit

Permalink
feat: align part of compile time binary evaluation with webpack (#7187)
Browse files Browse the repository at this point in the history
  • Loading branch information
LingyuCoder authored Jul 16, 2024
1 parent 1764b5f commit ba0f426
Show file tree
Hide file tree
Showing 17 changed files with 393 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fn get_typeof_evaluate_of_api(sym: &str) -> Option<&str> {
WEBPACK_CHUNK_NAME => Some("string"),
WEBPACK_RUNTIME_ID => None,
RSPACK_VERSION => Some("string"),
RSPACK_UNIQUE_ID => Some("string"),
_ => None,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
use rspack_core::{ConstDependency, SpanExt};
use rspack_error::miette::Severity;
use swc_core::common::{Span, Spanned};
use swc_core::ecma::ast::MemberProp;
use url::Url;

use super::JavascriptParserPlugin;
use crate::visitors::create_traceable_error;
use crate::utils::eval;
use crate::visitors::expr_name;
use crate::visitors::ExportedVariableInfo;
use crate::visitors::JavascriptParser;
use crate::visitors::{create_traceable_error, RootName};

// Port from https://github.com/webpack/webpack/blob/main/lib/dependencies/ImportMetaPlugin.js
// TODO:
// - scan `import.meta.webpack`
// - scan `import.meta.url.indexOf("index.js")`
// - evaluate expression. eg `import.meta.env && import.meta.env.xx` should be `false`
// - add warning for `import.meta`
pub struct ImportMetaPlugin;

impl JavascriptParserPlugin for ImportMetaPlugin {
fn evaluate_typeof(
&self,
_parser: &mut JavascriptParser,
expr: &swc_core::ecma::ast::UnaryExpr,
for_name: &str,
) -> Option<crate::utils::eval::BasicEvaluatedExpression> {
let mut evaluated = None;
if for_name == expr_name::IMPORT_META {
evaluated = Some("object".to_string());
} else if for_name == expr_name::IMPORT_META_URL {
evaluated = Some("string".to_string());
} else if for_name == expr_name::IMPORT_META_WEBPACK {
evaluated = Some("number".to_string())
} else if let Some(member_expr) = expr.arg.as_member()
&& let Some(meta_expr) = member_expr.obj.as_meta_prop()
&& meta_expr
.get_root_name()
.is_some_and(|name| name == expr_name::IMPORT_META)
&& (match &member_expr.prop {
MemberProp::Ident(_) => true,
MemberProp::Computed(computed) => computed.expr.is_lit(),
_ => false,
})
{
evaluated = Some("undefined".to_string())
}
evaluated.map(|e| eval::evaluate_to_string(e, expr.span.real_lo(), expr.span.real_hi()))
}

fn evaluate_identifier(
&self,
_parser: &mut JavascriptParser,
ident: &str,
start: u32,
end: u32,
) -> Option<eval::BasicEvaluatedExpression> {
if ident == expr_name::IMPORT_META_WEBPACK {
Some(eval::evaluate_to_number(5_f64, start, end))
} else {
None
}
}

fn r#typeof(
&self,
parser: &mut JavascriptParser,
Expand All @@ -44,6 +87,16 @@ impl JavascriptParserPlugin for ImportMetaPlugin {
None,
)));
Some(true)
} else if for_name == expr_name::IMPORT_META_WEBPACK {
parser
.presentational_dependencies
.push(Box::new(ConstDependency::new(
unary_expr.span().real_lo(),
unary_expr.span().real_hi(),
"'number'".into(),
None,
)));
Some(true)
} else {
None
}
Expand Down Expand Up @@ -102,6 +155,17 @@ impl JavascriptParserPlugin for ImportMetaPlugin {
None,
)));
Some(true)
} else if for_name == expr_name::IMPORT_META_WEBPACK {
// import.meta.webpack
parser
.presentational_dependencies
.push(Box::new(ConstDependency::new(
member_expr.span().real_lo(),
member_expr.span().real_hi(),
"5".to_string().into(),
None,
)));
Some(true)
} else {
None
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,88 @@ fn handle_add(
Some(res)
}

#[inline(always)]
pub fn handle_const_operation(
left: BasicEvaluatedExpression,
expr: &BinExpr,
scanner: &mut JavascriptParser,
) -> Option<BasicEvaluatedExpression> {
if !left.is_compile_time_value() {
return None;
}
let right = scanner.evaluate_expression(&expr.right);
if !right.is_compile_time_value() {
return None;
}

let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi().0);
res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects());

match expr.op {
BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Exp => {
if let Some(left_number) = left.as_number()
&& let Some(right_number) = right.as_number()
{
res.set_number(match expr.op {
BinaryOp::Sub => left_number - right_number,
BinaryOp::Mul => left_number * right_number,
BinaryOp::Div => left_number / right_number,
BinaryOp::Exp => left_number.powf(right_number),
_ => unreachable!(),
});
Some(res)
} else {
None
}
}
BinaryOp::BitAnd | BinaryOp::BitXor | BinaryOp::BitOr | BinaryOp::LShift | BinaryOp::RShift => {
if let Some(left_number) = left.as_int()
&& let Some(right_number) = right.as_int()
{
res.set_number(match expr.op {
BinaryOp::BitAnd => left_number & right_number,
BinaryOp::BitXor => left_number ^ right_number,
BinaryOp::BitOr => left_number | right_number,
BinaryOp::LShift => left_number << right_number,
BinaryOp::RShift => left_number >> right_number,
_ => unreachable!(),
} as f64);
Some(res)
} else {
None
}
}
BinaryOp::Lt | BinaryOp::Gt | BinaryOp::LtEq | BinaryOp::GtEq => {
if left.is_string() && right.is_string() {
let left_str = left.string();
let right_str = right.string();
res.set_bool(match expr.op {
BinaryOp::Lt => left_str < right_str,
BinaryOp::LtEq => left_str <= right_str,
BinaryOp::Gt => left_str > right_str,
BinaryOp::GtEq => left_str >= right_str,
_ => unreachable!(),
});
Some(res)
} else if let Some(left_number) = left.as_number()
&& let Some(right_number) = right.as_number()
{
res.set_bool(match expr.op {
BinaryOp::Lt => left_number < right_number,
BinaryOp::LtEq => left_number <= right_number,
BinaryOp::Gt => left_number > right_number,
BinaryOp::GtEq => left_number >= right_number,
_ => unreachable!(),
});
Some(res)
} else {
None
}
}
_ => None,
}
}

pub fn eval_binary_expression(
scanner: &mut JavascriptParser,
expr: &BinExpr,
Expand All @@ -401,7 +483,7 @@ pub fn eval_binary_expression(
BinaryOp::LogicalAnd => handle_logical_and(left, expr, scanner),
BinaryOp::LogicalOr => handle_logical_or(left, expr, scanner),
BinaryOp::Add => handle_add(left, expr, scanner),
_ => None,
_ => handle_const_operation(left, expr, scanner),
}
.or_else(|| {
Some(BasicEvaluatedExpression::with_range(
Expand Down
36 changes: 34 additions & 2 deletions crates/rspack_plugin_javascript/src/utils/eval/eval_unary_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use swc_core::ecma::ast::{Lit, UnaryExpr, UnaryOp};

use super::BasicEvaluatedExpression;
use crate::parser_plugin::JavascriptParserPlugin;
use crate::visitors::{CallHooksName, JavascriptParser};
use crate::visitors::{CallHooksName, JavascriptParser, RootName};

#[inline]
fn eval_typeof(
Expand All @@ -21,9 +21,41 @@ fn eval_typeof(
})
{
return Some(res);
} else if let Some(meta_prop) = expr.arg.as_meta_prop()
&& let Some(res) = meta_prop.get_root_name().and_then(|name| {
name.call_hooks_name(parser, |parser, for_name| {
parser
.plugin_drive
.clone()
.evaluate_typeof(parser, expr, for_name)
})
})
{
return Some(res);
} else if let Some(member_expr) = expr.arg.as_member()
&& let Some(res) = member_expr.call_hooks_name(parser, |parser, for_name| {
parser
.plugin_drive
.clone()
.evaluate_typeof(parser, expr, for_name)
})
{
return Some(res);
} else if let Some(chain_expr) = expr.arg.as_opt_chain()
&& let Some(res) = chain_expr.call_hooks_name(parser, |parser, for_name| {
parser
.plugin_drive
.clone()
.evaluate_typeof(parser, expr, for_name)
})
{
return Some(res);
} else if expr.arg.as_fn_expr().is_some() {
let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0);
res.set_string("function".to_string());
return Some(res);
}

// TODO: if let `MetaProperty`, `MemberExpression` ...
let arg = parser.evaluate_expression(&expr.arg);
if arg.is_unknown() {
let arg = expr.arg.unwrap_parens();
Expand Down
36 changes: 35 additions & 1 deletion crates/rspack_plugin_javascript/src/utils/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,34 @@ impl BasicEvaluatedExpression {
}
}

pub fn as_number(&self) -> Option<f64> {
if self.is_bool() {
Some(if self.bool() { 1_f64 } else { 0_f64 })
} else if self.is_null() {
Some(0_f64)
} else if self.is_string() {
self.string().parse::<f64>().ok()
} else if self.is_number() {
Some(self.number())
} else {
None
}
}

pub fn as_int(&self) -> Option<i32> {
if self.is_bool() {
Some(if self.bool() { 1_i32 } else { 0_i32 })
} else if self.is_null() {
Some(0_i32)
} else if self.is_string() {
self.string().parse::<i32>().ok()
} else if self.is_number() {
Some(self.number() as i32)
} else {
None
}
}

pub fn as_string(&self) -> Option<std::string::String> {
if self.is_bool() {
Some(self.bool().to_string())
Expand Down Expand Up @@ -410,7 +438,7 @@ impl BasicEvaluatedExpression {
pub fn set_bool(&mut self, boolean: Boolean) {
self.ty = Ty::Boolean;
self.boolean = Some(boolean);
self.side_effects = true
self.side_effects = false
}

pub fn set_range(&mut self, start: u32, end: u32) {
Expand Down Expand Up @@ -558,6 +586,12 @@ pub fn evaluate_to_string(value: String, start: u32, end: u32) -> BasicEvaluated
eval
}

pub fn evaluate_to_number(value: f64, start: u32, end: u32) -> BasicEvaluatedExpression {
let mut eval = BasicEvaluatedExpression::with_range(start, end);
eval.set_number(value);
eval
}

pub fn evaluate_to_identifier(
identifier: String,
root_info: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use swc_core::ecma::atoms::Atom;
pub use self::context_dependency_helper::{create_context_dependency, ContextModuleScanResult};
pub use self::parser::{
estree::*, AllowedMemberTypes, CallExpressionInfo, CallHooksName, ExportedVariableInfo,
JavascriptParser, MemberExpressionInfo, TagInfoData, TopLevelScope,
JavascriptParser, MemberExpressionInfo, RootName, TagInfoData, TopLevelScope,
};
pub use self::util::*;
use crate::BoxJavascriptParserPlugin;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use swc_core::atoms::Atom;
use swc_core::{
atoms::Atom,
ecma::ast::{Expr, MemberExpr, OptChainExpr},
};

use super::{ExportedVariableInfo, JavascriptParser};
use super::{AllowedMemberTypes, ExportedVariableInfo, JavascriptParser, MemberExpressionInfo};
use crate::visitors::scope_info::{FreeName, VariableInfoId};

/// callHooksForName/callHooksForInfo in webpack
Expand Down Expand Up @@ -64,6 +67,49 @@ impl CallHooksName for ExportedVariableInfo {
}
}

impl CallHooksName for MemberExpr {
fn call_hooks_name<'parser, F, T>(&self, parser: &mut JavascriptParser, hook_call: F) -> Option<T>
where
F: Fn(&mut JavascriptParser, &str) -> Option<T>,
{
let Some(MemberExpressionInfo::Expression(expr_name)) =
parser.get_member_expression_info(self, AllowedMemberTypes::Expression)
else {
return None;
};

let members = expr_name.members;
if members.is_empty() {
expr_name.root_info.call_hooks_name(parser, hook_call)
} else {
expr_name.name.call_hooks_name(parser, hook_call)
}
}
}

impl CallHooksName for OptChainExpr {
fn call_hooks_name<'parser, F, T>(&self, parser: &mut JavascriptParser, hook_call: F) -> Option<T>
where
F: Fn(&mut JavascriptParser, &str) -> Option<T>,
{
let Some(MemberExpressionInfo::Expression(expr_name)) = parser
.get_member_expression_info_from_expr(
&Expr::OptChain(self.to_owned()),
AllowedMemberTypes::Expression,
)
else {
return None;
};

let members = expr_name.members;
if members.is_empty() {
expr_name.root_info.call_hooks_name(parser, hook_call)
} else {
expr_name.name.call_hooks_name(parser, hook_call)
}
}
}

fn call_hooks_info<F, T>(
id: VariableInfoId,
parser: &mut JavascriptParser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ pub mod expr_name {
pub const REQUIRE_RESOLVE_WEAK: &str = "require.resolveWeak";
pub const IMPORT_META: &str = "import.meta";
pub const IMPORT_META_URL: &str = "import.meta.url";
pub const IMPORT_META_WEBPACK: &str = "import.meta.webpack";
pub const IMPORT_META_WEBPACK_HOT: &str = "import.meta.webpackHot";
pub const IMPORT_META_WEBPACK_HOT_ACCEPT: &str = "import.meta.webpackHot.accept";
pub const IMPORT_META_WEBPACK_HOT_DECLINE: &str = "import.meta.webpackHot.decline";
Expand Down
Loading

2 comments on commit ba0f426

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ❌ failure
_selftest ✅ success
nx ✅ success
rspress ✅ success
rsbuild ✅ success
examples ✅ success

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-07-16 8645c8d) Current Change
10000_development-mode + exec 2.25 s ± 17 ms 2.23 s ± 25 ms -0.74 %
10000_development-mode_hmr + exec 698 ms ± 4.7 ms 698 ms ± 5.7 ms -0.06 %
10000_production-mode + exec 2.8 s ± 34 ms 2.81 s ± 27 ms +0.14 %
arco-pro_development-mode + exec 1.88 s ± 72 ms 1.91 s ± 99 ms +1.17 %
arco-pro_development-mode_hmr + exec 434 ms ± 3.2 ms 433 ms ± 2.6 ms -0.10 %
arco-pro_production-mode + exec 3.44 s ± 90 ms 3.5 s ± 75 ms +1.64 %
threejs_development-mode_10x + exec 1.75 s ± 14 ms 1.76 s ± 14 ms +0.28 %
threejs_development-mode_10x_hmr + exec 860 ms ± 12 ms 864 ms ± 9.9 ms +0.48 %
threejs_production-mode_10x + exec 5.72 s ± 28 ms 5.75 s ± 37 ms +0.40 %

Please sign in to comment.