diff --git a/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/new_treeshaking.snap b/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/new_treeshaking.snap index 7fbbccd3da40..cd5c6738cdc7 100644 --- a/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/new_treeshaking.snap +++ b/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/new_treeshaking.snap @@ -5,9 +5,9 @@ source: crates/rspack_testing/src/run_fixture.rs (self['webpackChunkwebpack'] = self['webpackChunkwebpack'] || []).push([["main"], { "./zh_locale.js": (function (__unused_webpack_module, exports, __webpack_require__) { "use strict"; -Object.defineProperty(exports, "__esModule", { +Object.defineProperty(exports, "__esModule", ({ value: true -}); +})); exports["default"] = void 0; /* eslint-disable no-template-curly-in-string */ var _default = {}; exports["default"] = _default; diff --git a/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/output.snap b/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/output.snap index e707f90a30b0..4040c312c3ad 100644 --- a/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/output.snap +++ b/crates/rspack/tests/tree-shaking/cjs-export-computed-property/snapshot/output.snap @@ -5,9 +5,9 @@ source: crates/rspack_testing/src/run_fixture.rs (self['webpackChunkwebpack'] = self['webpackChunkwebpack'] || []).push([["main"], { "./zh_locale.js": (function (__unused_webpack_module, exports, __webpack_require__) { "use strict"; -Object.defineProperty(exports, "__esModule", { +Object.defineProperty(exports, "__esModule", ({ value: true -}); +})); exports["default"] = void 0; /* eslint-disable no-template-curly-in-string */ var _default = {}; exports["default"] = _default; diff --git a/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/new_treeshaking.snap b/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/new_treeshaking.snap index 29afaa4f4550..90f4ba75b59e 100644 --- a/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/new_treeshaking.snap +++ b/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/new_treeshaking.snap @@ -12,7 +12,7 @@ __webpack_require__.r(__webpack_exports__); /*#__PURE__*/ (_lib__WEBPACK_IMPORTED_MODULE_0___namespace_cache || (_lib__WEBPACK_IMPORTED_MODULE_0___namespace_cache = __webpack_require__.t(_lib__WEBPACK_IMPORTED_MODULE_0__, 2))); }), "./lib.js": (function (__unused_webpack_module, exports, __webpack_require__) { -exports['a'] = 100000; +exports.a = 100000; }), },function(__webpack_require__) { diff --git a/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/output.snap b/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/output.snap index 29afaa4f4550..90f4ba75b59e 100644 --- a/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/output.snap +++ b/crates/rspack/tests/tree-shaking/export-all-from-side-effects-free-commonjs/snapshot/output.snap @@ -12,7 +12,7 @@ __webpack_require__.r(__webpack_exports__); /*#__PURE__*/ (_lib__WEBPACK_IMPORTED_MODULE_0___namespace_cache || (_lib__WEBPACK_IMPORTED_MODULE_0___namespace_cache = __webpack_require__.t(_lib__WEBPACK_IMPORTED_MODULE_0__, 2))); }), "./lib.js": (function (__unused_webpack_module, exports, __webpack_require__) { -exports['a'] = 100000; +exports.a = 100000; }), },function(__webpack_require__) { diff --git a/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/new_treeshaking.snap b/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/new_treeshaking.snap index c44edd8c1319..99c3f135dc61 100644 --- a/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/new_treeshaking.snap +++ b/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/new_treeshaking.snap @@ -27,7 +27,7 @@ __webpack_require__.el(/* ./bar */"./bar.js").then(__webpack_require__.bind(__we console.log(mod); }); const a = "a"; -exports.test = 30; +__webpack_exports__.test = 30; }), "./foo.js": (function (module, exports, __webpack_require__) { { diff --git a/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/output.snap b/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/output.snap index c44edd8c1319..99c3f135dc61 100644 --- a/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/output.snap +++ b/crates/rspack/tests/tree-shaking/tree-shaking-interop/snapshot/output.snap @@ -27,7 +27,7 @@ __webpack_require__.el(/* ./bar */"./bar.js").then(__webpack_require__.bind(__we console.log(mod); }); const a = "a"; -exports.test = 30; +__webpack_exports__.test = 30; }), "./foo.js": (function (module, exports, __webpack_require__) { { diff --git a/crates/rspack_core/src/dependency/mod.rs b/crates/rspack_core/src/dependency/mod.rs index cddda365c1d7..72f25e802de1 100644 --- a/crates/rspack_core/src/dependency/mod.rs +++ b/crates/rspack_core/src/dependency/mod.rs @@ -57,6 +57,8 @@ pub enum DependencyType { DynamicImportEager, // cjs require CjsRequire, + // cjs exports + CjsExports, // new URL("./foo", import.meta.url) NewUrl, // new Worker() @@ -108,6 +110,7 @@ impl DependencyType { DependencyType::EsmImportSpecifier => Cow::Borrowed("esm import specifier"), DependencyType::DynamicImport => Cow::Borrowed("dynamic import"), DependencyType::CjsRequire => Cow::Borrowed("cjs require"), + DependencyType::CjsExports => Cow::Borrowed("cjs exports"), DependencyType::NewUrl => Cow::Borrowed("new URL()"), DependencyType::NewWorker => Cow::Borrowed("new Worker()"), DependencyType::ImportMetaHotAccept => Cow::Borrowed("import.meta.webpackHot.accept"), diff --git a/crates/rspack_core/src/dependency/runtime_template.rs b/crates/rspack_core/src/dependency/runtime_template.rs index f10458046d0a..59ee7eb3e76c 100644 --- a/crates/rspack_core/src/dependency/runtime_template.rs +++ b/crates/rspack_core/src/dependency/runtime_template.rs @@ -61,7 +61,7 @@ pub fn export_from_import( format!("var {import_var}_namespace_cache;\n",), InitFragmentStage::StageHarmonyExports, -1, - InitFragmentKey::uniqie(), + InitFragmentKey::unique(), None, ) .boxed(), diff --git a/crates/rspack_core/src/exports_info.rs b/crates/rspack_core/src/exports_info.rs index 4b69a7271bdd..846b8889d9bc 100644 --- a/crates/rspack_core/src/exports_info.rs +++ b/crates/rspack_core/src/exports_info.rs @@ -349,7 +349,10 @@ impl ExportsInfoId { let info = self.get_read_only_export_info(&name, mg); info.get_used_name(&name, runtime).map(UsedName::Str) } - UsedName::Vec(_) => todo!(), + UsedName::Vec(_) => { + // TODO + Some(name.clone()) + } } } @@ -515,7 +518,7 @@ impl ExportsInfo { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum UsedName { Str(JsWord), Vec(Vec), diff --git a/crates/rspack_core/src/init_fragment.rs b/crates/rspack_core/src/init_fragment.rs index 781d987391a8..3bbee1470ef5 100644 --- a/crates/rspack_core/src/init_fragment.rs +++ b/crates/rspack_core/src/init_fragment.rs @@ -19,8 +19,8 @@ pub struct InitFragmentContents { pub end: Option, } -pub struct InitFragmentKeyUniqie; -pub type InitFragmentKeyUKey = rspack_database::Ukey; +pub struct InitFragmentKeyUnique; +pub type InitFragmentKeyUKey = rspack_database::Ukey; #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum InitFragmentKey { @@ -31,12 +31,12 @@ pub enum InitFragmentKey { AwaitDependencies, HarmonyCompatibility, ModuleDecorator(String /* module_id */), - Uniqie(InitFragmentKeyUKey), + Unique(InitFragmentKeyUKey), } impl InitFragmentKey { - pub fn uniqie() -> Self { - Self::Uniqie(rspack_database::Ukey::new()) + pub fn unique() -> Self { + Self::Unique(rspack_database::Ukey::new()) } } @@ -79,7 +79,7 @@ impl InitFragmentKey { | InitFragmentKey::HarmonyExportStar(_) | InitFragmentKey::ExternalModule(_) | InitFragmentKey::ModuleDecorator(_) => first(fragments), - InitFragmentKey::HarmonyCompatibility | InitFragmentKey::Uniqie(_) => { + InitFragmentKey::HarmonyCompatibility | InitFragmentKey::Unique(_) => { debug_assert!(fragments.len() == 1, "fragment = {:?}", self); first(fragments) } diff --git a/crates/rspack_core/src/runtime_globals.rs b/crates/rspack_core/src/runtime_globals.rs index d971ea87ee92..99b820145cef 100644 --- a/crates/rspack_core/src/runtime_globals.rs +++ b/crates/rspack_core/src/runtime_globals.rs @@ -220,6 +220,8 @@ bitflags! { * the System.register context object */ const SYSTEM_CONTEXT = 1 << 49; + + const THIS_AS_EXPORTS = 1 << 50; } } @@ -290,6 +292,7 @@ impl RuntimeGlobals { R::HARMONY_MODULE_DECORATOR => "__webpack_require__.hmd", R::NODE_MODULE_DECORATOR => "__webpack_require__.nmd", R::SYSTEM_CONTEXT => "__webpack_require__.y", + R::THIS_AS_EXPORTS => "top-level-this-exports", r => panic!( "Unexpected flag `{r:?}`. RuntimeGlobals should only be printed for one single flag." ), diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs new file mode 100644 index 000000000000..de7891e35fc8 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_exports_dependency.rs @@ -0,0 +1,240 @@ +use rspack_core::{ + property_access, AsModuleDependency, Dependency, DependencyCategory, DependencyId, + DependencyTemplate, DependencyType, ExportNameOrSpec, ExportsOfExportsSpec, ExportsSpec, + InitFragmentExt, InitFragmentKey, InitFragmentStage, ModuleGraph, NormalInitFragment, + RuntimeGlobals, TemplateContext, TemplateReplaceSource, UsedName, +}; + +#[derive(Debug, Clone)] +pub enum ExportsBase { + Exports, + ModuleExports, + This, + DefinePropertyExports, + DefinePropertyModuleExports, + DefinePropertyThis, +} + +impl ExportsBase { + pub fn is_exports(&self) -> bool { + matches!(self, Self::Exports | Self::DefinePropertyExports) + } + + pub fn is_module_exports(&self) -> bool { + matches!( + self, + Self::ModuleExports | Self::DefinePropertyModuleExports + ) + } + + pub fn is_this(&self) -> bool { + matches!(self, Self::This | Self::DefinePropertyThis) + } + + pub fn is_expression(&self) -> bool { + matches!(self, Self::Exports | Self::ModuleExports | Self::This) + } + + pub fn is_define_property(&self) -> bool { + matches!( + self, + Self::DefinePropertyExports | Self::DefinePropertyModuleExports | Self::DefinePropertyThis + ) + } +} + +#[derive(Debug, Clone)] +pub struct CommonJsExportsDependency { + id: DependencyId, + range: (u32, u32), + value_range: Option<(u32, u32)>, + base: ExportsBase, + names: UsedName, +} + +impl CommonJsExportsDependency { + pub fn new( + range: (u32, u32), + value_range: Option<(u32, u32)>, + base: ExportsBase, + names: UsedName, + ) -> Self { + Self { + id: DependencyId::new(), + range, + value_range, + base, + names, + } + } +} + +impl Dependency for CommonJsExportsDependency { + fn dependency_debug_name(&self) -> &'static str { + "CommonJsExportsDependency" + } + + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::CommonJS + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::CjsExports + } + + fn get_exports(&self, _mg: &ModuleGraph) -> Option { + Some(ExportsSpec { + exports: ExportsOfExportsSpec::Array(vec![ExportNameOrSpec::String(match &self.names { + UsedName::Str(name) => name.clone(), + UsedName::Vec(names) => names[0].clone(), + })]), + priority: None, + can_mangle: Some(false), // in webpack, object own property may not be mangled + terminal_binding: None, + from: None, + dependencies: None, + hide_export: None, + exclude_exports: None, + }) + } + + fn get_module_evaluation_side_effects_state( + &self, + _module_graph: &rspack_core::ModuleGraph, + _module_chain: &mut rustc_hash::FxHashSet, + ) -> rspack_core::ConnectionState { + rspack_core::ConnectionState::Bool(false) + } +} + +impl AsModuleDependency for CommonJsExportsDependency {} + +impl DependencyTemplate for CommonJsExportsDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + let TemplateContext { + compilation, + module, + runtime, + init_fragments, + runtime_requirements, + .. + } = code_generatable_context; + + let mgm = compilation + .module_graph + .module_graph_module_by_identifier(&module.identifier()) + .expect("should have mgm"); + + let used = compilation + .module_graph + .get_exports_info(&module.identifier()) + .id + .get_used_name(&compilation.module_graph, *runtime, self.names.clone()); + + let exports_argument = mgm.get_exports_argument(); + let module_argument = mgm.get_module_argument(); + + let base = if self.base.is_exports() { + runtime_requirements.insert(RuntimeGlobals::EXPORTS); + exports_argument.to_string() + } else if self.base.is_module_exports() { + runtime_requirements.insert(RuntimeGlobals::MODULE); + format!("{}.exports", module_argument) + } else if self.base.is_this() { + runtime_requirements.insert(RuntimeGlobals::THIS_AS_EXPORTS); + "this".to_string() + } else { + panic!("Unexpected base type"); + }; + + if self.base.is_expression() { + if let Some(used) = used { + source.replace( + self.range.0, + self.range.1, + &format!( + "{}{}", + base, + property_access( + match used { + UsedName::Str(name) => vec![name].into_iter(), + UsedName::Vec(names) => names.into_iter(), + }, + 0 + ) + ), + None, + ) + } else { + init_fragments.push( + NormalInitFragment::new( + "var __webpack_unused_export__;\n".to_string(), + InitFragmentStage::StageConstants, + 0, + InitFragmentKey::unique(), + None, + ) + .boxed(), + ); + source.replace( + self.range.0, + self.range.1, + "__webpack_unused_export__", + None, + ); + } + } else if self.base.is_define_property() { + if let Some(value_range) = self.value_range { + if let Some(used) = used { + if let UsedName::Vec(used) = used { + source.replace( + self.range.0, + value_range.0, + &format!( + "Object.defineProperty({}{}, {}, (", + base, + property_access(used[0..used.len() - 1].iter(), 0), + serde_json::to_string(&used.last()) + .expect("Unexpected render define property base") + ), + None, + ); + source.replace(value_range.1, self.range.1, "))", None); + } else { + panic!("Unexpected base type"); + } + } else { + init_fragments.push( + NormalInitFragment::new( + "var __webpack_unused_export__;\n".to_string(), + InitFragmentStage::StageConstants, + 0, + InitFragmentKey::unique(), + None, + ) + .boxed(), + ); + source.replace( + self.range.0, + value_range.0, + "__webpack_unused_export__ = (", + None, + ); + source.replace(value_range.1, self.range.1, ")", None); + } + } else { + panic!("Define property need value range"); + } + } else { + panic!("Unexpected base type"); + } + } +} diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs index 40a3cda0b2f2..5a8a34e0d094 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs @@ -1,4 +1,7 @@ +mod common_js_exports_dependency; mod common_js_require_dependency; +pub use common_js_exports_dependency::CommonJsExportsDependency; +pub use common_js_exports_dependency::ExportsBase; pub use common_js_require_dependency::CommonJsRequireDependency; mod require_resolve_dependency; pub use require_resolve_dependency::RequireResolveDependency; diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_compatibility_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_compatibility_dependency.rs index 4bfd3d5473f4..9b790c1ab61f 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_compatibility_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_compatibility_dependency.rs @@ -58,7 +58,7 @@ impl DependencyTemplate for HarmonyCompatibilityDependency { ), InitFragmentStage::StageAsyncBoundary, 0, - InitFragmentKey::uniqie(), + InitFragmentKey::unique(), Some(format!("\n__webpack_async_result__();\n}} catch(e) {{ __webpack_async_result__(e); }} }}{});", if matches!(mgm.build_meta.as_ref().map(|meta| meta.has_top_level_await), Some(true)) { ", 1" } else { "" })), ))); } diff --git a/crates/rspack_plugin_javascript/src/runtime.rs b/crates/rspack_plugin_javascript/src/runtime.rs index a9aff442722a..8d3170023726 100644 --- a/crates/rspack_plugin_javascript/src/runtime.rs +++ b/crates/rspack_plugin_javascript/src/runtime.rs @@ -104,18 +104,35 @@ fn render_module( runtime_requirements: Option<&RuntimeGlobals>, module_id: &str, ) -> Result { - // TODO unused exports_argument - let module_argument = { + let need_module = runtime_requirements.is_some_and(|r| r.contains(RuntimeGlobals::MODULE)); + // TODO: determine arguments by runtime requirements after aligning commonjs dependencies with webpack + // let need_exports = runtime_requirements.is_some_and(|r| r.contains(RuntimeGlobals::EXPORTS)); + // let need_require = runtime_requirements.is_some_and(|r| { + // r.contains(RuntimeGlobals::REQUIRE) || r.contains(RuntimeGlobals::REQUIRE_SCOPE) + // }); + let need_exports = true; + let need_require = true; + + let mut args = Vec::new(); + if need_module || need_exports || need_require { let module_argument = mgm.get_module_argument(); - if let Some(runtime_requirements) = runtime_requirements - && runtime_requirements.contains(RuntimeGlobals::MODULE) - { + args.push(if need_module { module_argument.to_string() } else { format!("__unused_webpack_{module_argument}") - } - }; - let exports_argument = mgm.get_exports_argument(); + }); + } + if need_exports || need_require { + let exports_argument = mgm.get_exports_argument(); + args.push(if need_exports { + exports_argument.to_string() + } else { + format!("__unused_webpack_{exports_argument}") + }); + } + if need_require { + args.push(RuntimeGlobals::REQUIRE.to_string()); + } let mut sources = ConcatSource::new([ RawSource::from(serde_json::to_string(module_id).map_err(|e| internal_error!(e.to_string()))?), RawSource::from(": "), @@ -124,8 +141,8 @@ fn render_module( sources.add(RawSource::from(format!("\n/* start::{} */\n", module_id))); } sources.add(RawSource::from(format!( - "(function ({module_argument}, {exports_argument}, {}) {{\n", - RuntimeGlobals::REQUIRE + "(function ({}) {{\n", + args.join(", ") ))); if let Some(build_info) = &mgm.build_info && build_info.strict diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/common_js_export_scanner.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/common_js_export_scanner.rs index 81b00a1c6596..c6c63b9e8744 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/common_js_export_scanner.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/common_js_export_scanner.rs @@ -1,9 +1,9 @@ use rspack_core::{ - BuildMeta, BuildMetaDefaultObject, BuildMetaExportsType, DependencyTemplate, ModuleType, - RuntimeGlobals, + extract_member_expression_chain, BoxDependency, BuildMeta, BuildMetaDefaultObject, + BuildMetaExportsType, DependencyTemplate, ModuleType, RuntimeGlobals, SpanExt, UsedName, }; use swc_core::{ - common::SyntaxContext, + common::{Spanned, SyntaxContext}, ecma::{ ast::{ AssignExpr, CallExpr, Callee, Expr, ExprOrSpread, Ident, Lit, MemberExpr, ModuleItem, @@ -14,9 +14,10 @@ use swc_core::{ }; use super::{expr_matcher, is_require_call_expr}; -use crate::dependency::ModuleDecoratorDependency; +use crate::dependency::{CommonJsExportsDependency, ExportsBase, ModuleDecoratorDependency}; pub struct CommonJsExportDependencyScanner<'a> { + dependencies: &'a mut Vec, presentational_dependencies: &'a mut Vec>, unresolved_ctxt: SyntaxContext, build_meta: &'a mut BuildMeta, @@ -30,6 +31,7 @@ pub struct CommonJsExportDependencyScanner<'a> { impl<'a> CommonJsExportDependencyScanner<'a> { pub fn new( + dependencies: &'a mut Vec, presentational_dependencies: &'a mut Vec>, unresolved_ctxt: SyntaxContext, build_meta: &'a mut BuildMeta, @@ -37,6 +39,7 @@ impl<'a> CommonJsExportDependencyScanner<'a> { parser_exports_state: &'a mut Option, ) -> Self { Self { + dependencies, presentational_dependencies, unresolved_ctxt, build_meta, @@ -107,32 +110,69 @@ impl Visit for CommonJsExportDependencyScanner<'_> { fn visit_assign_expr(&mut self, assign_expr: &AssignExpr) { if let PatOrExpr::Pat(box Pat::Expr(box expr)) = &assign_expr.left { - // exports.__esModule = true; - // module.exports.__esModule = true; - // this.__esModule = true; - if expr_matcher::is_module_exports_esmodule(expr) - || expr_matcher::is_exports_esmodule(expr) - || expr_matcher::is_this_esmodule(expr) - { - self.enable(); - self.check_namespace( - // const flagIt = () => (exports.__esModule = true); => stmt_level = 1, last_stmt_is_expr_stmt = false - // const flagIt = () => { exports.__esModule = true }; => stmt_level = 2, last_stmt_is_expr_stmt = true - // (exports.__esModule = true); => stmt_level = 1, last_stmt_is_expr_stmt = true - self.stmt_level == 1 && self.last_stmt_is_expr_stmt, - Some(&assign_expr.right), - ); - assign_expr.right.visit_children_with(self); - return; - } // exports.xxx = 1; - if self.is_exports_member_expr_start(expr) { - self.enable(); + // module.exports.xxx = 1; + // this.xxx = 1; + let is_exports_start = self.is_exports_member_expr_start(expr); + let is_module_exports_start = self.is_module_exports_member_expr_start(expr); + let is_this_start: bool = self.is_this_member_expr_start(expr); + + if is_exports_start || is_module_exports_start || is_this_start { + if is_exports_start { + self.enable(); + } + + let remaining_members = expr.as_member().map(|expr| { + extract_member_expression_chain(expr) + .members() + .iter() + .skip(if is_module_exports_start { 2 } else { 1 }) + .map(|n| n.0.clone()) + .collect::>() + }); + + if let Some(remaining_members) = remaining_members + && !remaining_members.is_empty() + { + // exports.__esModule = true; + // module.exports.__esModule = true; + // this.__esModule = true; + if let Some(first_member) = remaining_members.first() + && first_member == "__esModule" + { + self.check_namespace( + // const flagIt = () => (exports.__esModule = true); => stmt_level = 1, last_stmt_is_expr_stmt = false + // const flagIt = () => { exports.__esModule = true }; => stmt_level = 2, last_stmt_is_expr_stmt = true + // (exports.__esModule = true); => stmt_level = 1, last_stmt_is_expr_stmt = true + self.stmt_level == 1 && self.last_stmt_is_expr_stmt, + Some(&assign_expr.right), + ); + } + + self + .dependencies + .push(Box::new(CommonJsExportsDependency::new( + (expr.span().real_lo(), expr.span().real_hi()), + None, + if is_exports_start { + ExportsBase::Exports + } else if is_module_exports_start { + ExportsBase::ModuleExports + } else if is_this_start { + ExportsBase::This + } else { + panic!("Unexpected expr type"); + }, + UsedName::Vec(remaining_members), + ))); + } + assign_expr.right.visit_children_with(self); return; } if self.is_exports_or_module_exports_or_this_expr(expr) { self.enable(); + if is_require_call_expr(&assign_expr.right, self.unresolved_ctxt) { // exports = require('xx'); // module.exports = require('xx'); @@ -154,9 +194,9 @@ impl Visit for CommonJsExportDependencyScanner<'_> { fn visit_call_expr(&mut self, call_expr: &CallExpr) { if let Callee::Expr(expr) = &call_expr.callee { - // Object.defineProperty(exports, "__esModule", { value: true }); - // Object.defineProperty(module.exports, "__esModule", { value: true }); - // Object.defineProperty(this, "__esModule", { value: true }); + // Object.defineProperty(exports, "xxx", { value: 1 }); + // Object.defineProperty(module.exports, "xxx", { value: 1 }); + // Object.defineProperty(this, "xxx", { value: 1 }); if expr_matcher::is_object_define_property(expr) && let Some(ExprOrSpread { expr, .. }) = call_expr.args.first() && self.is_exports_or_module_exports_or_this_expr(expr) @@ -168,12 +208,33 @@ impl Visit for CommonJsExportDependencyScanner<'_> { expr: box Expr::Lit(Lit::Str(str)), .. }) = call_expr.args.get(1) - && str.value == "__esModule" { - self.check_namespace( - self.stmt_level == 1, - get_value_of_property_description(arg2), - ); + // Object.defineProperty(exports, "__esModule", { value: true }); + // Object.defineProperty(module.exports, "__esModule", { value: true }); + // Object.defineProperty(this, "__esModule", { value: true }); + if str.value == "__esModule" { + self.check_namespace( + self.stmt_level == 1, + get_value_of_property_description(arg2), + ); + } + + self + .dependencies + .push(Box::new(CommonJsExportsDependency::new( + (call_expr.span.real_lo(), call_expr.span.real_hi()), + Some((arg2.span().real_lo(), arg2.span().real_hi())), + if self.is_exports_expr(expr) { + ExportsBase::DefinePropertyExports + } else if expr_matcher::is_module_exports(expr) { + ExportsBase::DefinePropertyModuleExports + } else if self.is_this_expr(expr) { + ExportsBase::DefinePropertyThis + } else { + panic!("Unexpected expr type"); + }, + UsedName::Vec(vec![str.value.clone()]), + ))); } self.enter_call += 1; @@ -209,16 +270,41 @@ impl<'a> CommonJsExportDependencyScanner<'a> { } } + fn is_module_exports_member_expr_start(&self, mut expr: &Expr) -> bool { + loop { + match expr { + _ if expr_matcher::is_module_exports(expr) => return true, + Expr::Member(MemberExpr { obj, .. }) => expr = obj.as_ref(), + _ => return false, + } + } + } + + fn is_this_member_expr_start(&self, mut expr: &Expr) -> bool { + if self.enter_call != 0 { + return false; + } + loop { + match expr { + _ if self.is_this_expr(expr) => return true, + Expr::Member(MemberExpr { obj, .. }) => expr = obj.as_ref(), + _ => return false, + } + } + } + fn is_exports_or_module_exports_or_this_expr(&self, expr: &Expr) -> bool { - matches!(expr, Expr::Ident(ident) if &ident.sym == "exports" && ident.span.ctxt == self.unresolved_ctxt) - || expr_matcher::is_module_exports(expr) - || matches!(expr, Expr::This(_) if self.enter_call == 0) + self.is_exports_expr(expr) || expr_matcher::is_module_exports(expr) || self.is_this_expr(expr) } fn is_exports_expr(&self, expr: &Expr) -> bool { matches!(expr, Expr::Ident(ident) if &ident.sym == "exports" && ident.span.ctxt == self.unresolved_ctxt) } + fn is_this_expr(&self, expr: &Expr) -> bool { + matches!(expr, Expr::This(_) if self.enter_call == 0) + } + fn check_namespace(&mut self, top_level: bool, value_expr: Option<&Expr>) { if matches!(self.parser_exports_state, Some(false)) || self.parser_exports_state.is_none() { return; diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs index d14f07880b4f..77a4dd288a7a 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs @@ -112,6 +112,7 @@ pub fn scan_dependencies( )); program.visit_with(&mut RequireContextScanner::new(&mut dependencies)); program.visit_with(&mut CommonJsExportDependencyScanner::new( + &mut dependencies, &mut presentational_dependencies, unresolved_ctxt, build_meta, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs index de28fd0f0e91..73a41f7fb220 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs @@ -97,9 +97,6 @@ pub(crate) mod expr_matcher { is_import_meta_webpack_context: "import.meta.webpackContext", is_import_meta_url: "import.meta.url", is_import_meta: "import.meta", - is_exports_esmodule: "exports.__esModule", - is_this_esmodule: "this.__esModule", - is_module_exports_esmodule: "module.exports.__esModule", is_object_define_property: "Object.defineProperty", }); } diff --git a/packages/rspack/tests/Stats.test.ts b/packages/rspack/tests/Stats.test.ts index ad93a04ecec2..973afcbb7b75 100644 --- a/packages/rspack/tests/Stats.test.ts +++ b/packages/rspack/tests/Stats.test.ts @@ -79,7 +79,7 @@ describe("Stats", () => { - Rspack compiled with 1 error (720d278abb396fd4623f)" + Rspack compiled with 1 error (22193973344376247dbc)" `); }); diff --git a/packages/rspack/tests/__snapshots__/Stats.test.ts.snap b/packages/rspack/tests/__snapshots__/Stats.test.ts.snap index 6d30e8f5d122..97a6f1f23252 100644 --- a/packages/rspack/tests/__snapshots__/Stats.test.ts.snap +++ b/packages/rspack/tests/__snapshots__/Stats.test.ts.snap @@ -421,7 +421,7 @@ exports.c = require("./c?c=3"); "errors": [], "errorsCount": 0, "filteredModules": undefined, - "hash": "592ad362eb9d0efc897b", + "hash": "f141b204a28da3455a73", "logging": {}, "modules": [ {