diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs index 574497472065..c7d95534a801 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs @@ -12,6 +12,8 @@ use crate::utils::eval::{self, BasicEvaluatedExpression}; use crate::visitors::{expr_matcher, scanner_context_module, JavascriptParser}; use crate::visitors::{extract_require_call_info, is_require_call_start}; +pub const COMMONJS_REQUIRE: &str = "require"; + pub struct CommonJsImportsParserPlugin; impl CommonJsImportsParserPlugin { @@ -94,15 +96,17 @@ impl CommonJsImportsParserPlugin { &self, parser: &mut JavascriptParser, call_expr: &CallExpr, + for_name: &str, ) -> Option<(Vec, Vec)> { if call_expr.args.len() != 1 { return None; } - let is_require_expr = call_expr.callee.as_expr().is_some_and(|expr| { - (expr_matcher::is_require(expr) && parser.is_unresolved_require(expr)) - || expr_matcher::is_module_require(expr) - }); + let is_require_expr = for_name == COMMONJS_REQUIRE + || call_expr + .callee + .as_expr() + .is_some_and(|expr| expr_matcher::is_module_require(expr)); if !is_require_expr { return None; @@ -159,6 +163,30 @@ impl CommonJsImportsParserPlugin { } impl JavascriptParserPlugin for CommonJsImportsParserPlugin { + fn can_rename(&self, parser: &mut JavascriptParser, str: &str) -> Option { + if str == COMMONJS_REQUIRE && parser.is_unresolved_ident(str) { + Some(true) + } else { + None + } + } + + fn rename(&self, parser: &mut JavascriptParser, expr: &Expr, str: &str) -> Option { + if str == COMMONJS_REQUIRE && parser.is_unresolved_ident(str) { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span().real_lo(), + expr.span().real_hi(), + "undefined".into(), + None, + ))); + Some(false) + } else { + None + } + } + fn evaluate_typeof( &self, parser: &mut JavascriptParser, @@ -166,13 +194,33 @@ impl JavascriptParserPlugin for CommonJsImportsParserPlugin { start: u32, end: u32, ) -> Option { - if expression.sym.as_str() == "require" && parser.is_unresolved_ident("require") { + if expression.sym.as_str() == COMMONJS_REQUIRE && parser.is_unresolved_ident(COMMONJS_REQUIRE) { Some(eval::evaluate_to_string("function".to_string(), start, end)) } else { None } } + fn evaluate_identifier( + &self, + parser: &mut JavascriptParser, + ident: &str, + start: u32, + end: u32, + ) -> Option { + if ident == COMMONJS_REQUIRE && parser.is_unresolved_ident(COMMONJS_REQUIRE) { + Some(eval::evaluate_to_identifier( + COMMONJS_REQUIRE.to_string(), + COMMONJS_REQUIRE.to_string(), + Some(true), + start, + end, + )) + } else { + None + } + } + fn expression_logical_operator( &self, parser: &mut JavascriptParser, @@ -240,14 +288,14 @@ impl JavascriptParserPlugin for CommonJsImportsParserPlugin { &self, parser: &mut JavascriptParser, call_expr: &CallExpr, - _for_name: &str, + for_name: &str, ) -> Option { let Callee::Expr(expr) = &call_expr.callee else { return Some(false); }; - let deps = self.require_handler(parser, call_expr); - - if let Some((commonjs_require_deps, require_helper_deps)) = deps { + if let Some((commonjs_require_deps, require_helper_deps)) = + self.require_handler(parser, call_expr, for_name) + { for dep in commonjs_require_deps { parser.dependencies.push(Box::new(dep)) } diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/drive.rs b/crates/rspack_plugin_javascript/src/parser_plugin/drive.rs index a011c6d0c864..d39c96e7bfe1 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/drive.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/drive.rs @@ -68,23 +68,6 @@ impl JavascriptParserPlugin for JavaScriptParserPluginDrive { None } - fn evaluate_typeof( - &self, - parser: &mut JavascriptParser, - ident: &swc_core::ecma::ast::Ident, - start: u32, - end: u32, - ) -> Option { - for plugin in &self.plugins { - let res = plugin.evaluate_typeof(parser, ident, start, end); - // `SyncBailHook` - if res.is_some() { - return res; - } - } - None - } - fn call(&self, parser: &mut JavascriptParser, expr: &CallExpr, name: &str) -> Option { for plugin in &self.plugins { let res = plugin.call(parser, expr, name); @@ -274,4 +257,65 @@ impl JavascriptParserPlugin for JavaScriptParserPluginDrive { } None } + + fn evaluate_typeof( + &self, + parser: &mut JavascriptParser, + ident: &swc_core::ecma::ast::Ident, + start: u32, + end: u32, + ) -> Option { + for plugin in &self.plugins { + let res = plugin.evaluate_typeof(parser, ident, start, end); + // `SyncBailHook` + if res.is_some() { + return res; + } + } + None + } + + fn evaluate_identifier( + &self, + parser: &mut JavascriptParser, + ident: &str, + start: u32, + end: u32, + ) -> Option { + for plugin in &self.plugins { + let res = plugin.evaluate_identifier(parser, ident, start, end); + // `SyncBailHook` + if res.is_some() { + return res; + } + } + None + } + + fn can_rename(&self, parser: &mut JavascriptParser, str: &str) -> Option { + for plugin in &self.plugins { + let res = plugin.can_rename(parser, str); + // `SyncBailHook` + if res.is_some() { + return res; + } + } + None + } + + fn rename( + &self, + parser: &mut JavascriptParser, + expr: &swc_core::ecma::ast::Expr, + str: &str, + ) -> Option { + for plugin in &self.plugins { + let res = plugin.rename(parser, expr, str); + // `SyncBailHook` + if res.is_some() { + return res; + } + } + None + } } diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs b/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs index 65ac873560c1..30f44506cb2a 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs @@ -18,21 +18,21 @@ mod webpack_included_plugin; /// TODO: should move to rspack_plugin_javascript once we drop old treeshaking mod worker_syntax_plugin; -pub use self::api_plugin::APIPlugin; -pub use self::check_var_decl::CheckVarDeclaratorIdent; -pub use self::common_js_exports_parse_plugin::CommonJsExportsParserPlugin; -pub use self::common_js_imports_parse_plugin::CommonJsImportsParserPlugin; -pub use self::common_js_plugin::CommonJsPlugin; -pub use self::compatibility_plugin::CompatibilityPlugin; -pub use self::drive::JavaScriptParserPluginDrive; -pub use self::exports_info_api_plugin::ExportsInfoApiPlugin; -pub use self::harmony_detection_parser_plugin::HarmonDetectionParserPlugin; -pub use self::harmony_top_level_this_plugin::HarmonyTopLevelThisParserPlugin; -pub use self::node_stuff_plugin::NodeStuffPlugin; -pub use self::provide::ProviderPlugin; -pub use self::r#const::{is_logic_op, ConstPlugin}; -pub use self::r#trait::{BoxJavascriptParserPlugin, JavascriptParserPlugin}; -pub use self::require_context_dependency_parser_plugin::RequireContextDependencyParserPlugin; -pub use self::url_plugin::URLPlugin; -pub use self::webpack_included_plugin::WebpackIsIncludedPlugin; -pub use self::worker_syntax_plugin::WorkerSyntaxScanner; +pub(crate) use self::api_plugin::APIPlugin; +pub(crate) use self::check_var_decl::CheckVarDeclaratorIdent; +pub(crate) use self::common_js_exports_parse_plugin::CommonJsExportsParserPlugin; +pub(crate) use self::common_js_imports_parse_plugin::CommonJsImportsParserPlugin; +pub(crate) use self::common_js_plugin::CommonJsPlugin; +pub(crate) use self::compatibility_plugin::CompatibilityPlugin; +pub(crate) use self::drive::JavaScriptParserPluginDrive; +pub(crate) use self::exports_info_api_plugin::ExportsInfoApiPlugin; +pub(crate) use self::harmony_detection_parser_plugin::HarmonDetectionParserPlugin; +pub(crate) use self::harmony_top_level_this_plugin::HarmonyTopLevelThisParserPlugin; +pub(crate) use self::node_stuff_plugin::NodeStuffPlugin; +pub(crate) use self::provide::ProviderPlugin; +pub(crate) use self::r#const::{is_logic_op, ConstPlugin}; +pub(crate) use self::r#trait::{BoxJavascriptParserPlugin, JavascriptParserPlugin}; +pub(crate) use self::require_context_dependency_parser_plugin::RequireContextDependencyParserPlugin; +pub(crate) use self::url_plugin::URLPlugin; +pub(crate) use self::webpack_included_plugin::WebpackIsIncludedPlugin; +pub(crate) use self::worker_syntax_plugin::WorkerSyntaxScanner; diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/trait.rs b/crates/rspack_plugin_javascript/src/parser_plugin/trait.rs index 2d91d108aa3b..96c350264ad4 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/trait.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/trait.rs @@ -1,5 +1,5 @@ use swc_core::ecma::ast::{ - AssignExpr, AwaitExpr, BinExpr, CallExpr, ForOfStmt, Ident, IfStmt, MemberExpr, ModuleDecl, + AssignExpr, AwaitExpr, BinExpr, CallExpr, Expr, ForOfStmt, Ident, IfStmt, MemberExpr, ModuleDecl, }; use swc_core::ecma::ast::{NewExpr, Program, Stmt, ThisExpr, UnaryExpr, VarDecl, VarDeclarator}; @@ -23,6 +23,14 @@ pub trait JavascriptParserPlugin { /// The return value will have no effect. fn top_level_for_of_await_stmt(&self, _parser: &mut JavascriptParser, _stmt: &ForOfStmt) {} + fn can_rename(&self, _parser: &mut JavascriptParser, _str: &str) -> Option { + None + } + + fn rename(&self, _parser: &mut JavascriptParser, _expr: &Expr, _str: &str) -> Option { + None + } + fn program(&self, _parser: &mut JavascriptParser, _ast: &Program) -> Option { None } @@ -46,6 +54,16 @@ pub trait JavascriptParserPlugin { None } + fn evaluate_identifier( + &self, + _parser: &mut JavascriptParser, + _ident: &str, + _start: u32, + _end: u32, + ) -> Option { + None + } + fn call( &self, _parser: &mut JavascriptParser, diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/webpack_included_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/webpack_included_plugin.rs index 610548bf9619..39d289566fdd 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/webpack_included_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/webpack_included_plugin.rs @@ -15,14 +15,8 @@ fn is_webpack_is_included(ident: &Ident) -> bool { pub struct WebpackIsIncludedPlugin; impl JavascriptParserPlugin for WebpackIsIncludedPlugin { - fn call(&self, parser: &mut JavascriptParser<'_>, expr: &CallExpr, _name: &str) -> Option { - let is_webpack_is_included = expr - .callee - .as_expr() - .and_then(|expr| expr.as_ident()) - .map(is_webpack_is_included) - .unwrap_or_default(); - if !is_webpack_is_included || expr.args.len() != 1 || expr.args[0].spread.is_some() { + fn call(&self, parser: &mut JavascriptParser<'_>, expr: &CallExpr, name: &str) -> Option { + if name != WEBPACK_IS_INCLUDED || expr.args.len() != 1 || expr.args[0].spread.is_some() { return None; } diff --git a/crates/rspack_plugin_javascript/src/utils/eval/eval_binary_expr.rs b/crates/rspack_plugin_javascript/src/utils/eval/eval_binary_expr.rs index adf9f0d7cac5..66679b98c1ef 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/eval_binary_expr.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/eval_binary_expr.rs @@ -188,17 +188,15 @@ fn handle_logical_and( let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi().0); let left = scanner.evaluate_expression(&expr.left); - match left.as_bool() { Some(true) => { - let right = scanner.evaluate_expression(&expr.right); // true && unknown = unknown - right.as_bool().map(|b| { - // true && right = right - res.set_bool(b); - res.set_side_effects(left.could_have_side_effects() || right.could_have_side_effects()); - res - }) + let mut right = scanner.evaluate_expression(&expr.right); + if left.could_have_side_effects() { + right.set_side_effects(true) + } + right.set_range(expr.span.real_lo(), expr.span.hi.0); + Some(right) } Some(false) => { // false && any = false diff --git a/crates/rspack_plugin_javascript/src/utils/eval/mod.rs b/crates/rspack_plugin_javascript/src/utils/eval/mod.rs index f083d55e28bc..4c5d23fd73ef 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/mod.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/mod.rs @@ -259,6 +259,24 @@ impl BasicEvaluatedExpression { self.side_effects = false } + pub fn set_truthy(&mut self) { + self.falsy = false; + self.truthy = true; + self.nullish = Some(false); + } + + pub fn set_falsy(&mut self) { + self.falsy = true; + self.truthy = false; + } + + pub fn set_nullish(&mut self, nullish: bool) { + self.nullish = Some(nullish); + if nullish { + self.set_falsy() + } + } + pub fn set_items(&mut self, items: Vec) { self.ty = Ty::Array; self.side_effects = items.iter().any(|item| item.could_have_side_effects()); @@ -365,6 +383,29 @@ pub fn evaluate_to_string(value: String, start: u32, end: u32) -> BasicEvaluated eval } +pub fn evaluate_to_identifier( + identifier: String, + root_info: String, + truthy: Option, + start: u32, + end: u32, +) -> BasicEvaluatedExpression { + let mut eval = BasicEvaluatedExpression::with_range(start, end); + eval.set_identifier(identifier, ExportedVariableInfo::Name(root_info)); + eval.set_side_effects(false); + match truthy { + Some(v) => { + if v { + eval.set_truthy(); + } else { + eval.set_falsy(); + } + } + None => eval.set_nullish(true), + }; + eval +} + bitflags! { struct RegExpFlag: u8 { const FLAG_Y = 1 << 0; diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs index 5aa0bd39b960..0be741061e65 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -379,6 +379,16 @@ impl<'parser> JavascriptParser<'parser> { self.definitions_db.set(definitions, name, info); } + fn set_variable(&mut self, name: String, variable: String) { + let id = self.definitions; + if name == variable { + self.definitions_db.delete(id, &name); + } else { + let variable = VariableInfo::new(id, Some(FreeName::String(variable)), None); + self.definitions_db.set(id, name, variable); + } + } + fn undefined_variable(&mut self, name: String) { self.definitions_db.delete(self.definitions, name) } @@ -666,6 +676,7 @@ impl JavascriptParser<'_> { match self.evaluating(expr) { Some(evaluated) => { if evaluated.is_compile_time_value() { + // TODO: delete this arm let _ = self.ignored.insert(DependencyLocation::new( expr.span().real_lo(), expr.span().real_hi(), @@ -700,23 +711,30 @@ impl JavascriptParser<'_> { None } Expr::Ident(ident) => { + let drive = self.plugin_drive.clone(); let Some(info) = self.get_variable_info(&ident.sym) else { - let mut eval = - BasicEvaluatedExpression::with_range(ident.span.real_lo(), ident.span().hi().0); - eval.set_identifier( - ident.sym.to_string(), - ExportedVariableInfo::Name(ident.sym.to_string()), - ); - return Some(eval); + // use `ident.sym` as fallback for global variable(or maybe just a undefined variable) + return drive + .evaluate_identifier( + self, + ident.sym.as_str(), + ident.span.real_lo(), + ident.span.hi.0, + ) + .or_else(|| { + let mut eval = + BasicEvaluatedExpression::with_range(ident.span.real_lo(), ident.span.hi.0); + eval.set_identifier( + ident.sym.to_string(), + ExportedVariableInfo::Name(ident.sym.to_string()), + ); + Some(eval) + }); }; - if matches!(info.free_name, Some(FreeName::String(_))) { - let mut eval = - BasicEvaluatedExpression::with_range(ident.span.real_lo(), ident.span().hi().0); - eval.set_identifier( - ident.sym.to_string(), - ExportedVariableInfo::VariableInfo(info.id()), - ); - return Some(eval); + if let Some(FreeName::String(name)) = info.free_name.as_ref() { + // avoid ownership + let name = name.to_string(); + return drive.evaluate_identifier(self, &name, ident.span.real_lo(), ident.span.hi.0); } None } diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs index eb543754e5d7..3af4ea33f72d 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs @@ -375,17 +375,24 @@ impl<'parser> JavascriptParser<'parser> { fn walk_variable_declaration(&mut self, decl: &VarDecl) { self.enter_assign = true; for declarator in &decl.decls { - // if let Some(renamed_identifier) = declarator - // .init - // .as_ref() - // .and_then(|init| self.get_rename_identifier(&init)) - // && let Some(name) = declarator.name.as_ident() - // { - // // TODO: can_rename hook - // // TODO: rename hook - - // // if declarator.is_synthesized() - // } + if let Some(init) = declarator.init.as_ref() + && let Some(renamed_identifier) = self.get_rename_identifier(init) + && let Some(ident) = declarator.name.as_ident() + { + let drive = self.plugin_drive.clone(); + if drive + .can_rename(self, &renamed_identifier) + .unwrap_or_default() + { + if !drive + .rename(self, init, &renamed_identifier) + .unwrap_or_default() + { + self.set_variable(ident.sym.to_string(), renamed_identifier); + } + continue; + } + } if !self .plugin_drive .clone() @@ -397,8 +404,8 @@ impl<'parser> JavascriptParser<'parser> { self.walk_expression(init); } } - self.enter_assign = false; } + self.enter_assign = false; } fn walk_expression_statement(&mut self, stmt: &ExprStmt) { @@ -734,12 +741,12 @@ impl<'parser> JavascriptParser<'parser> { } } - // fn get_rename_identifier(&mut self, expr: &Expr) -> Option { - // let result = self.evaluate_expression(expr); - // result - // .is_identifier() - // .then(|| result.identifier().to_string()) - // } + fn get_rename_identifier(&mut self, expr: &Expr) -> Option { + let result = self.evaluate_expression(expr); + result + .is_identifier() + .then(|| result.identifier().to_string()) + } fn walk_assignment_expression(&mut self, expr: &AssignExpr) { self.enter_assign = true; diff --git a/crates/rspack_plugin_javascript/src/visitors/scope_info.rs b/crates/rspack_plugin_javascript/src/visitors/scope_info.rs index c371dbf8dc2d..d9ca9992fe9d 100644 --- a/crates/rspack_plugin_javascript/src/visitors/scope_info.rs +++ b/crates/rspack_plugin_javascript/src/visitors/scope_info.rs @@ -240,13 +240,6 @@ impl VariableInfo { tag_info.data = data; } - pub fn free_name(&self) -> &FreeName { - self - .free_name - .as_ref() - .expect("make sure `free_name` exist") - } - fn set_id(&mut self, id: VariableInfoId) { self.id = Some(id); } diff --git a/packages/rspack/tests/cases/parsing/issue-5498/foo.js b/packages/rspack/tests/cases/parsing/issue-5498/foo.js new file mode 100644 index 000000000000..e7134e7006d9 --- /dev/null +++ b/packages/rspack/tests/cases/parsing/issue-5498/foo.js @@ -0,0 +1 @@ +module.exports = "foo"; diff --git a/packages/rspack/tests/cases/parsing/issue-5498/index.js b/packages/rspack/tests/cases/parsing/issue-5498/index.js new file mode 100644 index 000000000000..49687fbc775a --- /dev/null +++ b/packages/rspack/tests/cases/parsing/issue-5498/index.js @@ -0,0 +1,15 @@ +it("require alias should works", () => { + const r = typeof require === "function" && require; + expect(r("./foo")).toBe("foo"); + + if (typeof require === "function") { + // call expression is ok + expect(require("./foo")).toBe("foo"); + } + + if (typeof require === "function") { + // identifier case 2 + const r = require; + expect(r("./foo")).toBe("foo"); + } +}); diff --git a/packages/rspack/tests/cases/parsing/renaming/index.js b/packages/rspack/tests/cases/parsing/renaming/index.js index 59093fb4b02b..92ddf9fdb887 100644 --- a/packages/rspack/tests/cases/parsing/renaming/index.js +++ b/packages/rspack/tests/cases/parsing/renaming/index.js @@ -2,10 +2,10 @@ it("should be able to rename require by var", function () { var cjsRequire; // just to make it difficult var cjsRequire = require, cjsRequire2 = typeof require !== "undefined" && require; - // expect(cjsRequire("./file")).toBe("ok"); - // expect(cjsRequire2("./file")).toBe("ok"); - expect(typeof cjsRequire).toBe("function"); - expect(typeof cjsRequire2).toBe("function"); + expect(typeof cjsRequire).toBe("undefined"); + expect(typeof cjsRequire2).toBe("undefined"); + expect(cjsRequire("./file")).toBe("ok"); + expect(cjsRequire2("./file")).toBe("ok"); }); // it("should be able to rename require by assign", function() {