diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index f921214705e..85d56f7f798 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1723,6 +1723,7 @@ export interface RawOptions { experiments: RawExperiments node?: RawNodeOption profile: boolean + amd?: string bail: boolean __references: Record } diff --git a/crates/rspack_binding_options/src/options/mod.rs b/crates/rspack_binding_options/src/options/mod.rs index a82b3f39363..e2fb75b51e4 100644 --- a/crates/rspack_binding_options/src/options/mod.rs +++ b/crates/rspack_binding_options/src/options/mod.rs @@ -54,6 +54,7 @@ pub struct RawOptions { pub experiments: RawExperiments, pub node: Option, pub profile: bool, + pub amd: Option, pub bail: bool, #[napi(js_name = "__references", ts_type = "Record")] pub __references: References, @@ -93,6 +94,7 @@ impl TryFrom for CompilerOptions { optimization, node, profile: value.profile, + amd: value.amd, bail: value.bail, __references: value.__references, }) diff --git a/crates/rspack_core/src/dependency/dependency_category.rs b/crates/rspack_core/src/dependency/dependency_category.rs index 94baaf42cf3..5f0de90bcda 100644 --- a/crates/rspack_core/src/dependency/dependency_category.rs +++ b/crates/rspack_core/src/dependency/dependency_category.rs @@ -7,6 +7,7 @@ pub enum DependencyCategory { Unknown, Esm, CommonJS, + Amd, Url, CssImport, CssCompose, @@ -41,6 +42,7 @@ impl DependencyCategory { DependencyCategory::Unknown => "unknown", DependencyCategory::Esm => "esm", DependencyCategory::CommonJS => "commonjs", + DependencyCategory::Amd => "amd", DependencyCategory::Url => "url", DependencyCategory::CssImport => "css-import", DependencyCategory::CssCompose => "css-compose", diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 94ca7c89846..7787f12de8d 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -33,6 +33,12 @@ pub enum DependencyType { CjsExportRequire, // cjs self reference CjsSelfReference, + // AMD + AmdDefine, + AmdRequireArray, + AmdRequireContext, + AmdRequire, + AmdRequireItem, // new URL("./foo", import.meta.url) NewUrl, // new Worker() @@ -126,6 +132,11 @@ impl DependencyType { DependencyType::CjsExports => "cjs exports", DependencyType::CjsExportRequire => "cjs export require", DependencyType::CjsSelfReference => "cjs self exports reference", + DependencyType::AmdDefine => "amd define", + DependencyType::AmdRequireArray => "amd require array", + DependencyType::AmdRequireContext => "amd require context", + DependencyType::AmdRequire => "amd", + DependencyType::AmdRequireItem => "amd require", DependencyType::NewUrl => "new URL()", DependencyType::NewWorker => "new Worker()", DependencyType::CreateScriptUrl => "create script url", diff --git a/crates/rspack_core/src/options/compiler_options.rs b/crates/rspack_core/src/options/compiler_options.rs index e960928154d..96d95e704b6 100644 --- a/crates/rspack_core/src/options/compiler_options.rs +++ b/crates/rspack_core/src/options/compiler_options.rs @@ -18,6 +18,7 @@ pub struct CompilerOptions { pub node: Option, pub optimization: Optimization, pub profile: bool, + pub amd: Option, pub bail: bool, pub __references: References, } diff --git a/crates/rspack_core/src/runtime_globals.rs b/crates/rspack_core/src/runtime_globals.rs index e6927fe847c..1266caa6c99 100644 --- a/crates/rspack_core/src/runtime_globals.rs +++ b/crates/rspack_core/src/runtime_globals.rs @@ -250,6 +250,10 @@ bitflags! { const RSPACK_UNIQUE_ID = 1 << 65; const HAS_FETCH_PRIORITY = 1 << 66; + + // amd module support + const AMD_DEFINE = 1 << 67; + const AMD_OPTIONS = 1 << 68; } } @@ -295,6 +299,8 @@ impl RuntimeGlobals { R::GET_CHUNK_UPDATE_CSS_FILENAME => "__webpack_require__.hk", R::HMR_MODULE_DATA => "__webpack_require__.hmrD", R::HMR_RUNTIME_STATE_PREFIX => "__webpack_require__.hmrS", + R::AMD_DEFINE => "__webpack_require__.amdD", + R::AMD_OPTIONS => "__webpack_require__.amdO", R::EXTERNAL_INSTALL_CHUNK => "__webpack_require__.C", R::GET_FULL_HASH => "__webpack_require__.h", R::GLOBAL => "__webpack_require__.g", diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/amd_define_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/amd_define_dependency.rs new file mode 100644 index 00000000000..41b08089f47 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/amd_define_dependency.rs @@ -0,0 +1,302 @@ +use bitflags::bitflags; +use rspack_core::{ + AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyCategory, + DependencyId, DependencyTemplate, DependencyType, RuntimeGlobals, RuntimeSpec, TemplateContext, + TemplateReplaceSource, +}; +use rspack_util::{atom::Atom, json_stringify}; + +use super::local_module::LocalModule; + +bitflags! { + #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] + struct Branch: u8 { + const L = 1 << 0; + const A = 1 << 1; + const O = 1 << 2; + const F = 1 << 3; + } +} + +impl Branch { + pub fn get_requests(&self) -> RuntimeGlobals { + match *self { + f if f == Branch::F => { + RuntimeGlobals::REQUIRE | RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE + } + o if o == Branch::O => RuntimeGlobals::MODULE, + o_f if o_f == (Branch::O | Branch::F) => { + RuntimeGlobals::REQUIRE | RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE + } + a_f if a_f == (Branch::A | Branch::F) => RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE, + a_o if a_o == (Branch::A | Branch::O) => RuntimeGlobals::MODULE, + a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => { + RuntimeGlobals::EXPORTS | RuntimeGlobals::MODULE + } + l_f if l_f == (Branch::L | Branch::F) => RuntimeGlobals::REQUIRE | RuntimeGlobals::MODULE, + l_o if l_o == (Branch::L | Branch::O) => RuntimeGlobals::empty(), + l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => { + RuntimeGlobals::REQUIRE | RuntimeGlobals::MODULE + } + l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => RuntimeGlobals::empty(), + l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => RuntimeGlobals::empty(), + l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => { + RuntimeGlobals::empty() + } + _ => RuntimeGlobals::empty(), + } + } + + pub fn get_definition(&self, local_module_var: &Option) -> String { + let name = match local_module_var { + Some(name) => name, + None => "XXX", + }; + match *self { + f if f == Branch::F => "var __WEBPACK_AMD_DEFINE_RESULT__;".to_string(), + o if o == Branch::O => "".to_string(), + o_f if o_f == (Branch::O | Branch::F) => { + "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string() + } + a_f if a_f == (Branch::A | Branch::F) => { + "var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string() + } + a_o if a_o == (Branch::A | Branch::O) => "".to_string(), + a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => "var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;".to_string(), + l_f if l_f == (Branch::L | Branch::F) => { + format!("var {}, {}module;", name, name) + }, + l_o if l_o == (Branch::L | Branch::O) => { + format!("var {};", name) + }, + l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => { + format!("var {}, {}factory, {}module;", name, name, name) + }, + l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => { + format!("var __WEBPACK_AMD_DEFINE_ARRAY__, {}, {}exports;", name, name) + }, + l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O)=> { + format!("var {};", name) + }, + l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => { + format!("var {}array, {}factory, {}exports, {};", name, name, name, name) + }, + _ => "".to_string(), + } + } + + pub fn get_content( + &self, + local_module_var: &Option, + named_module: &Option, + ) -> String { + let local_module_var = match local_module_var { + Some(name) => name, + None => "XXX", + }; + let named_module = match named_module { + Some(name) => name, + None => "YYY", + }; + match *self { + f if f == Branch::F => { + format!( + "!(__WEBPACK_AMD_DEFINE_RESULT__ = (#).call(exports, {require}, exports, module), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))", + require = RuntimeGlobals::REQUIRE.name() + ) + } + o if o == Branch::O => "!(module.exports = #)".to_string(), + o_f if o_f == (Branch::O | Branch::F) => { + format!( + "!(__WEBPACK_AMD_DEFINE_FACTORY__ = (#), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, {require}, exports, module)) : + __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))", + require = RuntimeGlobals::REQUIRE.name() + ) + } + a_f if a_f == (Branch::A | Branch::F) => "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_RESULT__ = (#).apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))".to_string(), + a_o if a_o == (Branch::A | Branch::O) => "!(#, module.exports = #)".to_string(), + a_o_f if a_o_f == (Branch::A | Branch::O | Branch::F) => { + "!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, __WEBPACK_AMD_DEFINE_FACTORY__ = (#), + __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? + (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), + __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__))".to_string() + } + l_f if l_f == (Branch::L | Branch::F) => { + format!( + "!({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}, {var_name} = (#).call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module), {var_name}module.loaded = true, {var_name} === undefined && ({var_name} = {var_name}module.exports))", + var_name = local_module_var, + module_id = json_stringify(named_module), + require = RuntimeGlobals::REQUIRE.name(), + ) + } + l_o if l_o == (Branch::L | Branch::O) => format!("!({} = #)", local_module_var), + l_o_f if l_o_f == (Branch::L | Branch::O | Branch::F) => { + format!( + "!({var_name}factory = (#), (typeof {var_name}factory === 'function' ? (({var_name}module = {{ id: {module_id}, exports: {{}}, loaded: false }}), ({var_name} = {var_name}factory.call({var_name}module.exports, {require}, {var_name}module.exports, {var_name}module)), ({var_name}module.loaded = true), {var_name} === undefined && ({var_name} = {var_name}module.exports)) : {var_name} = {var_name}factory))", + var_name = local_module_var, + module_id = json_stringify(named_module), + require = RuntimeGlobals::REQUIRE.name(), + ) + } + l_a_f if l_a_f == (Branch::L | Branch::A | Branch::F) => format!("!(__WEBPACK_AMD_DEFINE_ARRAY__ = #, {} = (#).apply({}exports = {{}}, __WEBPACK_AMD_DEFINE_ARRAY__), {} === undefined && ({} = {}exports))", local_module_var, local_module_var, local_module_var, local_module_var, local_module_var), + l_a_o if l_a_o == (Branch::L | Branch::A | Branch::O) => format!("!(#, {} = #)", local_module_var), + l_a_o_f if l_a_o_f == (Branch::L | Branch::A | Branch::O | Branch::F) => format!( + "!({var_name}array = #, {var_name}factory = (#), + (typeof {var_name}factory === 'function' ? + (({var_name} = {var_name}factory.apply({var_name}exports = {{}}, {var_name}array)), {var_name} === undefined && ({var_name} = {var_name}exports)) : + ({var_name} = {var_name}factory) + ))", + var_name = local_module_var, + ), + _ => "".to_string(), + } + } +} + +#[derive(Debug, Clone)] +pub struct AmdDefineDependency { + id: DependencyId, + range: (u32, u32), + array_range: Option<(u32, u32)>, + function_range: Option<(u32, u32)>, + object_range: Option<(u32, u32)>, + named_module: Option, + local_module: Option, +} + +impl AmdDefineDependency { + pub fn new( + range: (u32, u32), + array_range: Option<(u32, u32)>, + function_range: Option<(u32, u32)>, + object_range: Option<(u32, u32)>, + named_module: Option, + local_module: Option, + ) -> Self { + Self { + id: DependencyId::new(), + range, + array_range, + function_range, + object_range, + named_module, + local_module, + } + } + + pub fn get_local_module_mut(&mut self) -> Option<&mut LocalModule> { + self.local_module.as_mut() + } +} + +impl Dependency for AmdDefineDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Amd + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::AmdDefine + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl AmdDefineDependency { + fn local_module_var(&self) -> Option { + self.local_module.as_ref().and_then(|m| { + if m.is_used() { + Some(m.variable_name()) + } else { + None + } + }) + } + + fn branch(&self) -> Branch { + let mut ret = Branch::empty(); + if self.local_module.as_ref().is_some_and(|m| m.is_used()) { + ret |= Branch::L; + } + if self.array_range.is_some() { + ret |= Branch::A; + } + if self.object_range.is_some() { + ret |= Branch::O; + } + if self.function_range.is_some() { + ret |= Branch::F; + } + ret + } +} + +impl DependencyTemplate for AmdDefineDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + let branch = self.branch(); + code_generatable_context + .runtime_requirements + .insert(branch.get_requests()); + + let local_module_var = self.local_module_var(); + + let text = branch.get_content(&local_module_var, &self.named_module); + let definition = branch.get_definition(&local_module_var); + + let mut texts = text.split('#'); + + if !definition.is_empty() { + source.insert(0, &definition, None); + } + + let mut current = self.range.0; + if let Some(array_range) = self.array_range { + source.replace(current, array_range.0, texts.next().unwrap_or(""), None); + current = array_range.1; + } + + if let Some(object_range) = self.object_range { + source.replace(current, object_range.0, texts.next().unwrap_or(""), None); + current = object_range.1; + } else if let Some(function_range) = self.function_range { + source.replace(current, function_range.0, texts.next().unwrap_or(""), None); + current = function_range.1; + } + + source.replace(current, self.range.1, texts.next().unwrap_or(""), None); + + if texts.next().is_some() { + panic!("Implementation error"); + } + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for AmdDefineDependency {} + +impl AsContextDependency for AmdDefineDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_array_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_array_dependency.rs new file mode 100644 index 00000000000..4772ebaadc3 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_array_dependency.rs @@ -0,0 +1,111 @@ +use itertools::Itertools; +use rspack_core::{ + module_raw, AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, + DependencyCategory, DependencyId, DependencyTemplate, DependencyType, ModuleDependency, + RuntimeSpec, TemplateContext, TemplateReplaceSource, +}; +use rspack_util::atom::Atom; + +use super::{ + amd_require_item_dependency::AMDRequireItemDependency, + local_module_dependency::LocalModuleDependency, +}; + +#[derive(Debug, Clone)] +pub enum AmdDep { + String(Atom), + LocalModuleDependency(LocalModuleDependency), + AMDRequireItemDependency(AMDRequireItemDependency), +} + +#[derive(Debug, Clone)] +pub struct AmdRequireArrayDependency { + id: DependencyId, + deps_array: Vec, + range: (u32, u32), +} + +impl AmdRequireArrayDependency { + pub fn new(deps_array: Vec, range: (u32, u32)) -> Self { + Self { + id: DependencyId::new(), + deps_array, + range, + } + } +} + +impl Dependency for AmdRequireArrayDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Amd + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::AmdRequireArray + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl AmdRequireArrayDependency { + fn get_content(&self, code_generatable_context: &mut TemplateContext) -> String { + format!( + "[{}]", + self + .deps_array + .iter() + .map(|dep| Self::content_for_dependency(dep, code_generatable_context)) + .join(", ") + ) + } + + fn content_for_dependency( + dep: &AmdDep, + code_generatable_context: &mut TemplateContext, + ) -> String { + match dep { + AmdDep::String(name) => name.to_string(), + AmdDep::LocalModuleDependency(dep) => dep.get_variable_name(), + AmdDep::AMDRequireItemDependency(dep) => module_raw( + code_generatable_context.compilation, + code_generatable_context.runtime_requirements, + dep.id(), + dep.request(), + dep.weak(), + ), + } + } +} + +impl DependencyTemplate for AmdRequireArrayDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + let content = self.get_content(code_generatable_context); + source.replace(self.range.0, self.range.1, &content, None); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for AmdRequireArrayDependency {} + +impl AsContextDependency for AmdRequireArrayDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_dependency.rs new file mode 100644 index 00000000000..fb121e64c95 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_dependency.rs @@ -0,0 +1,199 @@ +use rspack_core::{ + block_promise, AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, + DependencyCategory, DependencyId, DependencyTemplate, DependencyType, RuntimeGlobals, + RuntimeSpec, +}; + +#[derive(Debug, Clone)] +pub struct AMDRequireDependency { + id: DependencyId, + outer_range: (u32, u32), + // In the webpack source code, type annotation of `arrayRange` is non-null. + // However, `DependencyTemplate` implementation assumes `arrayRange` can be null in some cases. + // So I use Option here. + array_range: Option<(u32, u32)>, + function_range: Option<(u32, u32)>, + error_callback_range: Option<(u32, u32)>, + pub function_bind_this: bool, + pub error_callback_bind_this: bool, +} + +impl AMDRequireDependency { + pub fn new( + outer_range: (u32, u32), + array_range: Option<(u32, u32)>, + function_range: Option<(u32, u32)>, + error_callback_range: Option<(u32, u32)>, + ) -> Self { + Self { + id: DependencyId::new(), + outer_range, + array_range, + function_range, + error_callback_range, + function_bind_this: false, + error_callback_bind_this: false, + } + } +} + +impl Dependency for AMDRequireDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Amd + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::AmdRequire + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl DependencyTemplate for AMDRequireDependency { + fn apply( + &self, + source: &mut rspack_core::TemplateReplaceSource, + code_generatable_context: &mut rspack_core::TemplateContext, + ) { + let module_graph = code_generatable_context.compilation.get_module_graph(); + let block = module_graph.get_parent_block(&self.id); + + let promise = block_promise( + block, + code_generatable_context.runtime_requirements, + code_generatable_context.compilation, + "AMD require", + ); + + // has array range but no function range + if let Some(array_range) = self.array_range + && self.function_range.is_none() + { + let start_block = promise + ".then(function() {"; + let end_block = format!( + ";}})['catch']{}", + RuntimeGlobals::UNCAUGHT_ERROR_HANDLER.name() + ); + code_generatable_context + .runtime_requirements + .insert(RuntimeGlobals::UNCAUGHT_ERROR_HANDLER); + source.replace(self.outer_range.0, array_range.0, &start_block, None); + source.replace(array_range.1, self.outer_range.1, &end_block, None); + return; + } + + // has function range but no array range + if let Some(function_range) = self.function_range + && self.array_range.is_none() + { + let start_block = promise + ".then(("; + let end_block = format!( + ").bind(exports, {}, exports, module))['catch']({})", + RuntimeGlobals::REQUIRE.name(), + RuntimeGlobals::UNCAUGHT_ERROR_HANDLER.name() + ); + code_generatable_context + .runtime_requirements + .insert(RuntimeGlobals::UNCAUGHT_ERROR_HANDLER); + source.replace(self.outer_range.0, function_range.0, &start_block, None); + source.replace(function_range.1, self.outer_range.1, &end_block, None); + return; + } + + // has array range, function range, and errorCallbackRange + if let Some(array_range) = self.array_range + && let Some(function_range) = self.function_range + && let Some(error_callback_range) = self.error_callback_range + { + let start_block = promise + ".then(function() { "; + let error_range_block = if self.function_bind_this { + "}.bind(this))['catch'](" + } else { + "})['catch'](" + }; + let end_block = if self.error_callback_bind_this { + ".bind(this))" + } else { + ")" + }; + + source.replace(self.outer_range.0, array_range.0, &start_block, None); + + source.insert(array_range.0, "var __WEBPACK_AMD_REQUIRE_ARRAY__ = ", None); + + source.replace(array_range.1, function_range.0, "; (", None); + + source.insert( + function_range.1, + ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);", + None, + ); + + source.replace( + function_range.1, + error_callback_range.0, + error_range_block, + None, + ); + + source.replace(error_callback_range.1, self.outer_range.1, end_block, None); + + return; + } + + // has array range, function range, but no errorCallbackRange + if let Some(array_range) = self.array_range + && let Some(function_range) = self.function_range + { + let start_block = promise + ".then(function() { "; + let end_block = format!( + "}}{})['catch']({})", + if self.function_bind_this { + ".bind(this)" + } else { + "" + }, + RuntimeGlobals::UNCAUGHT_ERROR_HANDLER.name() + ); + code_generatable_context + .runtime_requirements + .insert(RuntimeGlobals::UNCAUGHT_ERROR_HANDLER); + + source.replace(self.outer_range.0, array_range.0, &start_block, None); + + source.insert(array_range.0, "var __WEBPACK_AMD_REQUIRE_ARRAY__ = ", None); + + source.replace(array_range.1, function_range.0, "; (", None); + + source.insert( + function_range.1, + ").apply(null, __WEBPACK_AMD_REQUIRE_ARRAY__);", + None, + ); + + source.replace(function_range.1, self.outer_range.1, &end_block, None); + }; + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for AMDRequireDependency {} + +impl AsContextDependency for AMDRequireDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_item_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_item_dependency.rs new file mode 100644 index 00000000000..b30461cb707 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/amd_require_item_dependency.rs @@ -0,0 +1,89 @@ +use rspack_core::{ + module_raw, AffectType, AsContextDependency, Compilation, Dependency, DependencyCategory, + DependencyId, DependencyTemplate, DependencyType, ModuleDependency, RuntimeSpec, TemplateContext, + TemplateReplaceSource, +}; +use rspack_util::atom::Atom; + +#[derive(Debug, Clone)] +pub struct AMDRequireItemDependency { + id: DependencyId, + request: Atom, + range: (u32, u32), + optional: bool, +} + +impl AMDRequireItemDependency { + pub fn new(request: Atom, range: (u32, u32)) -> Self { + Self { + id: DependencyId::new(), + request, + range, + optional: false, + } + } + + pub fn set_optional(&mut self, optional: bool) { + self.optional = optional; + } +} + +impl Dependency for AMDRequireItemDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Amd + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::AmdRequireItem + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::True + } +} + +impl DependencyTemplate for AMDRequireItemDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + // ModuleDependencyTemplateAsRequireId + let content = module_raw( + code_generatable_context.compilation, + code_generatable_context.runtime_requirements, + &self.id, + &self.request, + self.weak(), + ); + source.replace(self.range.0, self.range.1, &content, None); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl ModuleDependency for AMDRequireItemDependency { + fn request(&self) -> &str { + &self.request + } + + fn get_optional(&self) -> bool { + self.optional + } +} + +impl AsContextDependency for AMDRequireItemDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/local_module.rs b/crates/rspack_plugin_javascript/src/dependency/amd/local_module.rs new file mode 100644 index 00000000000..f531d94b7fc --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/local_module.rs @@ -0,0 +1,38 @@ +use rspack_util::atom::Atom; + +#[derive(Debug, Clone)] +pub struct LocalModule { + name: Atom, + idx: usize, + used: bool, +} + +impl LocalModule { + pub fn new(name: Atom, idx: usize) -> Self { + Self { + name, + idx, + used: false, + } + } + + pub fn flag_used(&mut self) { + self.used = true; + } + + pub fn variable_name(&self) -> String { + format!("__WEBPACK_LOCAL_MODULE_{}__", self.idx) + } + + pub fn is_used(&self) -> bool { + self.used + } + + pub fn get_name(&self) -> &Atom { + &self.name + } + + pub fn get_idx(&self) -> usize { + self.idx + } +} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/local_module_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/local_module_dependency.rs new file mode 100644 index 00000000000..319b2740653 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/local_module_dependency.rs @@ -0,0 +1,75 @@ +use rspack_core::{ + AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyId, + DependencyTemplate, RuntimeSpec, TemplateContext, TemplateReplaceSource, +}; + +use super::local_module::LocalModule; + +#[derive(Debug, Clone)] +pub struct LocalModuleDependency { + id: DependencyId, + local_module: LocalModule, + range: Option<(u32, u32)>, + call_new: bool, +} + +impl LocalModuleDependency { + pub fn new(local_module: LocalModule, range: Option<(u32, u32)>, call_new: bool) -> Self { + Self { + id: DependencyId::new(), + local_module, + range, + call_new, + } + } + + pub fn get_variable_name(&self) -> String { + self.local_module.variable_name() + } +} + +impl Dependency for LocalModuleDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl DependencyTemplate for LocalModuleDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + _code_generatable_context: &mut TemplateContext, + ) { + if let Some(range) = self.range { + let module_instance = if self.call_new { + format!( + "new (function () {{ return {}; }})()", + self.local_module.variable_name() + ) + } else { + self.local_module.variable_name() + }; + source.replace(range.0, range.1, &module_instance, None); + } + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for LocalModuleDependency {} + +impl AsContextDependency for LocalModuleDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/mod.rs b/crates/rspack_plugin_javascript/src/dependency/amd/mod.rs new file mode 100644 index 00000000000..c15c3261683 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/mod.rs @@ -0,0 +1,7 @@ +pub mod amd_define_dependency; +pub mod amd_require_array_dependency; +pub mod amd_require_dependency; +pub mod amd_require_item_dependency; +pub mod local_module; +pub mod local_module_dependency; +pub mod unsupported_dependency; diff --git a/crates/rspack_plugin_javascript/src/dependency/amd/unsupported_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/amd/unsupported_dependency.rs new file mode 100644 index 00000000000..21eb584e2d0 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/amd/unsupported_dependency.rs @@ -0,0 +1,71 @@ +use rspack_core::{ + AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyCategory, + DependencyId, DependencyTemplate, DependencyType, RuntimeSpec, TemplateContext, + TemplateReplaceSource, +}; +use rspack_util::atom::Atom; + +#[derive(Debug, Clone)] +pub struct UnsupportedDependency { + id: DependencyId, + request: Atom, + range: (u32, u32), +} + +impl UnsupportedDependency { + pub fn new(request: Atom, range: (u32, u32)) -> Self { + Self { + id: DependencyId::new(), + request, + range, + } + } +} + +impl Dependency for UnsupportedDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Unknown + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::Unknown + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl DependencyTemplate for UnsupportedDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + _code_generatable_context: &mut TemplateContext, + ) { + let content = format!( + "Object(function webpackMissingModule() {{var e = new Error(\"Cannot find module '{}'\"); e.code = 'MODULE_NOT_FOUND'; throw e;}}())", + self.request + ); + source.replace(self.range.0, self.range.1, &content, None); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for UnsupportedDependency {} + +impl AsContextDependency for UnsupportedDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/mod.rs b/crates/rspack_plugin_javascript/src/dependency/mod.rs index d4b68d94d08..f6e8ebbadbf 100644 --- a/crates/rspack_plugin_javascript/src/dependency/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/mod.rs @@ -1,3 +1,4 @@ +mod amd; mod commonjs; mod context; mod esm; @@ -9,6 +10,7 @@ mod pure_expression_dependency; mod url; mod worker; +pub use self::amd::*; pub use self::commonjs::*; pub use self::context::*; pub use self::esm::*; diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/amd_define_dependency_parser_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/amd_define_dependency_parser_plugin.rs new file mode 100644 index 00000000000..fa8236bc2d4 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/parser_plugin/amd_define_dependency_parser_plugin.rs @@ -0,0 +1,597 @@ +use std::borrow::Cow; + +use rspack_core::{ + BuildMetaDefaultObject, BuildMetaExportsType, ConstDependency, RuntimeGlobals, SpanExt, +}; +use rspack_util::atom::Atom; +use rustc_hash::FxHashMap; +use swc_core::{ + common::{Span, Spanned}, + ecma::{ + ast::{BlockStmtOrExpr, CallExpr, Callee, Expr, Lit, Pat}, + utils::ExprExt, + }, +}; + +use super::JavascriptParserPlugin; +use crate::{ + dependency::{ + amd_define_dependency::AmdDefineDependency, + amd_require_item_dependency::AMDRequireItemDependency, + local_module_dependency::LocalModuleDependency, + }, + utils::eval::BasicEvaluatedExpression, + visitors::{scope_info::FreeName, JavascriptParser, Statement}, +}; + +pub struct AMDDefineDependencyParserPlugin; + +fn is_unbound_function_expression(expr: &Expr) -> bool { + expr.is_fn_expr() || expr.is_arrow() +} + +fn is_bound_function_expression(expr: &Expr) -> bool { + if !expr.is_call() { + return false; + } + + let call_expr = expr.as_call().expect("expr is supposed to be CallExpr"); + match &call_expr.callee { + Callee::Super(_) => return false, + Callee::Import(_) => return false, + Callee::Expr(callee) => { + if !callee.is_member() { + return false; + } + let callee_member = callee + .as_member() + .expect("callee is supposed to be MemberExpr"); + if callee_member.prop.is_computed() { + return false; + } + if !callee_member.obj.is_fn_expr() { + return false; + } + if !callee_member.prop.is_ident_with("bind") { + return false; + } + } + } + + true +} + +fn is_callable(expr: &Expr) -> bool { + is_unbound_function_expression(expr) || is_bound_function_expression(expr) +} + +/** + * lookup + * + * define('ui/foo/bar', ['./baz', '../qux'], ...); + * - 'ui/foo/baz' + * - 'ui/qux' + */ +fn resolve_mod_name(mod_name: &Option, dep_name: &str) -> Atom { + if let Some(mod_name) = mod_name + && dep_name.starts_with('.') + { + let mut path: Vec<&str> = mod_name.split('/').collect(); + path.pop(); + + for seg in dep_name.split('.') { + if seg == ".." { + path.pop(); + } else if seg != "." { + path.push(seg); + } + } + + path.join("/").into() + } else { + dep_name.into() + } +} + +const REQUIRE: &str = "require"; +const MODULE: &str = "module"; +const EXPORTS: &str = "exports"; +const RESERVED_NAMES: [&str; 3] = [REQUIRE, MODULE, EXPORTS]; + +fn span_to_range(span: Span) -> (u32, u32) { + (span.real_lo(), span.real_hi()) +} + +fn get_lit_str(expr: &Expr) -> Option { + expr.as_lit().and_then(|lit| match lit { + Lit::Str(s) => Some(s.value.clone()), + _ => None, + }) +} + +fn get_ident_name(pat: &Pat) -> Atom { + pat + .as_ident() + .map(|ident| ident.sym.clone()) + .unwrap_or("".into()) +} + +impl AMDDefineDependencyParserPlugin { + fn process_array( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + param: &BasicEvaluatedExpression, + identifiers: &mut FxHashMap, // param index => "require" | "module" | "exports" + named_module: &Option, + ) -> Option { + if param.is_array() { + let items = param.items(); + for (idx, item) in items.iter().enumerate() { + if item.is_string() { + let item = item.string(); + if let Some(i) = RESERVED_NAMES.iter().position(|s| s == item) { + identifiers.insert(idx, RESERVED_NAMES[i]); + } + } + let result = self.process_item(parser, call_expr, item, named_module); + if result.is_none() { + self.process_context(parser, call_expr, item); + } + } + return Some(true); + } + // currently, there is no ConstArray in rspack + // TODO: check if `param` is a const string array + None + } + + fn process_item( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + param: &BasicEvaluatedExpression, + named_module: &Option, + ) -> Option { + if param.is_conditional() { + let options = param.options(); + + for option in options.iter() { + let result = self.process_item(parser, call_expr, option, &None); + if result.is_none() { + self.process_context(parser, call_expr, param); + } + } + + return Some(true); + } else if param.is_string() { + let param_str = param.string(); + let range = { + let (l, h) = param.range(); + (l, h - 1) + }; + + let dep = if param_str == "require" { + Box::new(ConstDependency::new( + range.0, + range.1, + RuntimeGlobals::REQUIRE.name().into(), + Some(RuntimeGlobals::REQUIRE), + )) + } else if param_str == "exports" { + Box::new(ConstDependency::new( + range.0, + range.1, + EXPORTS.into(), + Some(RuntimeGlobals::EXPORTS), + )) + } else if param_str == "module" { + Box::new(ConstDependency::new( + range.0, + range.1, + MODULE.into(), + Some(RuntimeGlobals::MODULE), + )) + } else if let Some(local_module) = + parser.get_local_module_mut(&resolve_mod_name(named_module, param_str)) + { + local_module.flag_used(); + let dep = Box::new(LocalModuleDependency::new( + local_module.clone(), + Some((range.0, range.1)), + false, + )); + parser.presentational_dependencies.push(dep); + return Some(true); + } else { + let mut dep = Box::new(AMDRequireItemDependency::new( + Atom::new(param_str.as_str()), + range, + )); + dep.set_optional(parser.in_try); + parser.dependencies.push(dep); + return Some(true); + }; + // TODO: how to implement this? + // dep.loc = /** @type {DependencyLocation} */ (expr.loc); + parser.presentational_dependencies.push(dep); + return Some(true); + } + None + } + + fn process_context( + &self, + _parser: &mut JavascriptParser, + _call_expr: &CallExpr, + _param: &BasicEvaluatedExpression, + ) -> Option { + // TODO: support amd context dep + None + } + + fn process_call_define( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + ) -> Option { + let mut array: Option<&Expr> = None; + let mut func: Option<&Expr> = None; + let mut obj: Option<&Expr> = None; + let mut named_module: Option = None; + + match call_expr.args.len() { + 1 => { + let first_arg = &call_expr.args[0]; + + // We don't support spread syntax in `define()` + if first_arg.spread.is_some() { + return None; + } + + if is_callable(&first_arg.expr) { + // define(f() {…}) + func = Some(&first_arg.expr); + } else if first_arg.expr.is_object() { + // define({…}) + obj = Some(&first_arg.expr); + } else { + // define(expr) + // unclear if function or object + func = Some(&first_arg.expr); + obj = Some(&first_arg.expr); + } + } + 2 => { + let first_arg = &call_expr.args[0]; + let second_arg = &call_expr.args[1]; + + // We don't support spread syntax in `define()` + if first_arg.spread.is_some() || second_arg.spread.is_some() { + return None; + } + + if first_arg.expr.is_lit() { + // define("…", …) + named_module = get_lit_str(&first_arg.expr); + + if is_callable(&second_arg.expr) { + // define("…", f() {…}) + func = Some(&second_arg.expr); + } else if second_arg.expr.is_object() { + // define("…", {…}) + obj = Some(&second_arg.expr); + } else { + // define("…", expr) + // unclear if function or object + func = Some(&second_arg.expr); + obj = Some(&second_arg.expr); + } + } else { + // define([…], …) + if !first_arg.expr.is_array() { + return None; + } + + array = Some(&first_arg.expr); + + if is_callable(&second_arg.expr) { + // define([…], f() {}) + func = Some(&second_arg.expr); + } else if second_arg.expr.is_object() { + // define([…], {…}) + obj = Some(&second_arg.expr); + } else { + // define([…], expr) + // unclear if function or object + func = Some(&second_arg.expr); + obj = Some(&second_arg.expr); + } + } + } + 3 => { + // define("…", […], …) + + let first_arg = &call_expr.args[0]; + let second_arg = &call_expr.args[1]; + let third_arg = &call_expr.args[2]; + + // We don't support spread syntax in `define()` + if first_arg.spread.is_some() || second_arg.spread.is_some() || third_arg.spread.is_some() { + return None; + } + + if !first_arg.expr.is_lit() { + return None; + } + if !second_arg.expr.is_array_lit() { + return None; + } + + named_module = get_lit_str(&first_arg.expr); + array = Some(&second_arg.expr); + + if is_callable(&third_arg.expr) { + // define("…", […], f() {}) + func = Some(&third_arg.expr); + } else if third_arg.expr.is_object() { + // define("…", […], {…}) + obj = Some(&third_arg.expr); + } else { + // define("…", […], expr) + // unclear if function or object + func = Some(&third_arg.expr); + obj = Some(&third_arg.expr); + } + } + _ => return None, + } + + { + // DynamicExports.bailout(parser.state); + // TODO: consider how to share this code + if parser.parser_exports_state.is_some_and(|x| x) { + parser.build_meta.exports_type = BuildMetaExportsType::Unset; + parser.build_meta.default_object = BuildMetaDefaultObject::False; + } + parser.parser_exports_state = Some(false); + } + + let mut fn_params: Option>> = None; + let mut fn_params_offset = 0usize; + if let Some(func) = func { + if is_unbound_function_expression(func) { + fn_params = match func { + Expr::Fn(normal_func) => Some( + normal_func + .function + .params + .iter() + .map(|param| Cow::Borrowed(¶m.pat)) + .collect(), + ), + Expr::Arrow(array_func) => Some(array_func.params.iter().map(Cow::Borrowed).collect()), + _ => None, + }; + } else if is_bound_function_expression(func) { + let call_expr = func + .as_call() + .expect("call_expr is supposed to be a CallExpr"); + let object = &call_expr + .callee + .as_expr() + .expect("call_expr.callee is supposed to be Expr") + .as_member() + .expect("call_expr.callee is supposed to be MemberExpr") + .obj + .as_fn_expr() + .expect("call_expr.callee.obj is supposed to be FnExpr"); + + fn_params = Some( + object + .function + .params + .iter() + .map(|param| Cow::Borrowed(¶m.pat)) + .collect(), + ); + + if !call_expr.args.is_empty() { + fn_params_offset = call_expr.args.len() - 1; + } + } + } + + // TODO: ensure all fn_params are identifiers + + let mut fn_renames = FxHashMap::default(); + if let Some(array) = array { + let mut identifiers = FxHashMap::default(); + let param = parser.evaluate_expression(array); + let result = self.process_array(parser, call_expr, ¶m, &mut identifiers, &named_module); + if !result.is_some_and(|b| b) { + return None; + } + if let Some(fn_params) = &mut fn_params { + let mut i = 0usize; + fn_params.retain(|param| { + if i < fn_params_offset { + return false; + } + let idx = i - fn_params_offset; + i += 1; + if let Some(&name) = identifiers.get(&idx) { + fn_renames.insert(get_ident_name(param), name); + return false; + } + true + }); + } + } else if let Some(fn_params) = &mut fn_params { + let mut i = 0usize; + fn_params.retain(|param| { + if i < fn_params_offset { + return false; + } + let idx = i - fn_params_offset; + i += 1; + if idx < RESERVED_NAMES.len() { + fn_renames.insert(get_ident_name(param), RESERVED_NAMES[idx]); + return false; + } + true + }); + } + + if func.is_some_and(is_unbound_function_expression) { + let in_try = parser.in_try; + parser.in_function_scope( + true, + fn_params.expect("fn_params should not be None").into_iter(), + |parser| { + for (name, &rename_identifier) in fn_renames.iter() { + let variable = parser + .get_variable_info(rename_identifier) + .and_then(|info| info.free_name.as_ref()) + .and_then(|free_name| match free_name { + FreeName::String(s) => Some(s.to_string()), + FreeName::True => None, + }) + .unwrap_or(rename_identifier.to_string()); + parser.set_variable(name.to_string(), variable); + } + + parser.in_try = in_try; + + if let Some(func) = func.and_then(|f| f.as_fn_expr()) { + if let Some(body) = &func.function.body { + parser.detect_mode(&body.stmts); + let prev = parser.prev_statement; + parser.pre_walk_statement(Statement::Block(body)); + parser.prev_statement = prev; + parser.walk_statement(Statement::Block(body)); + } + } else if let Some(func) = func.and_then(|f| f.as_arrow()) { + match &*func.body { + BlockStmtOrExpr::BlockStmt(stmt) => { + parser.detect_mode(&stmt.stmts); + let prev = parser.prev_statement; + parser.pre_walk_statement(Statement::Block(stmt)); + parser.prev_statement = prev; + parser.walk_statement(Statement::Block(stmt)); + } + BlockStmtOrExpr::Expr(expr) => parser.walk_expression(expr), + } + } + }, + ); + } else if func.is_some_and(is_bound_function_expression) { + let in_try = parser.in_try; + + if let Some(call_expr) = func.and_then(|f| f.as_call()) { + let object = call_expr + .callee + .as_expr() + .and_then(|expr| expr.as_member()) + .and_then(|member_expr| member_expr.obj.as_fn_expr()); + + if let Some(func_expr) = object { + parser.in_function_scope( + true, + func_expr + .function + .params + .iter() + .map(|param| Cow::Borrowed(¶m.pat)) + .filter(|pat| { + pat + .as_ident() + .is_some_and(|ident| !RESERVED_NAMES.contains(&ident.sym.as_str())) + }), + |parser| { + for (name, &rename_identifier) in fn_renames.iter() { + let variable = parser + .get_variable_info(rename_identifier) + .and_then(|info| info.free_name.as_ref()) + .and_then(|free_name| match free_name { + FreeName::String(s) => Some(s.to_string()), + FreeName::True => None, + }) + .unwrap_or(rename_identifier.to_string()); + parser.set_variable(name.to_string(), variable); + } + + parser.in_try = in_try; + + if let Some(body) = &func_expr.function.body { + parser.detect_mode(&body.stmts); + let prev = parser.prev_statement; + parser.pre_walk_statement(Statement::Block(body)); + parser.prev_statement = prev; + parser.walk_statement(Statement::Block(body)); + } + }, + ); + } + + parser.walk_expr_or_spread(&call_expr.args); + } + } else if let Some(expr) = func { + parser.walk_expression(expr); + } else if let Some(expr) = obj { + parser.walk_expression(expr); + } + + let local_module = named_module + .as_ref() + .map(|name| parser.add_local_module(name.as_str())); + + let dep = Box::new(AmdDefineDependency::new( + (call_expr.span.real_lo(), call_expr.span.real_hi()), + array.map(|expr| span_to_range(expr.span())), + func.map(|expr| span_to_range(expr.span())), + obj.map(|expr| span_to_range(expr.span())), + named_module, + local_module, + )); + + parser.presentational_dependencies.push(dep); + + Some(true) + } +} + +impl JavascriptParserPlugin for AMDDefineDependencyParserPlugin { + fn call( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + for_name: &str, + ) -> Option { + if for_name == "define" { + self.process_call_define(parser, call_expr) + } else { + None + } + } + + /** + * unlike js, it's hard to share the LocalModule instance in Rust. + * so the AmdDefineDependency will get a clone of LocalModule in parser.local_modules. + * synchronize the used flag to the AmdDefineDependency's local_module at the end of the parse. + */ + fn finish(&self, parser: &mut JavascriptParser) -> Option { + for dep in parser.presentational_dependencies.iter_mut() { + if let Some(define_dep) = dep.as_any_mut().downcast_mut::() + && let Some(local_module) = define_dep.get_local_module_mut() + && parser + .local_modules + .get(local_module.get_idx()) + .is_some_and(|m| m.is_used()) + { + local_module.flag_used(); + } + } + None + } +} diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/amd_require_dependencies_block_parser_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/amd_require_dependencies_block_parser_plugin.rs new file mode 100644 index 00000000000..549f2b0ddef --- /dev/null +++ b/crates/rspack_plugin_javascript/src/parser_plugin/amd_require_dependencies_block_parser_plugin.rs @@ -0,0 +1,335 @@ +use std::{borrow::Cow, iter}; + +use either::Either; +use itertools::Itertools; +use rspack_core::{ + AsyncDependenciesBlock, BoxDependency, ConstDependency, DependencyLocation, DependencyRange, + RuntimeGlobals, SpanExt, +}; +use rspack_error::miette::Severity; +use rspack_util::atom::Atom; +use swc_core::{ + common::Spanned, + ecma::ast::{BlockStmtOrExpr, CallExpr, ExprOrSpread, Pat}, +}; + +use super::{ + require_ensure_dependencies_block_parse_plugin::GetFunctionExpression, JavascriptParserPlugin, +}; +use crate::{ + dependency::{ + amd_require_dependency::AMDRequireDependency, + amd_require_item_dependency::AMDRequireItemDependency, + local_module_dependency::LocalModuleDependency, unsupported_dependency::UnsupportedDependency, + }, + utils::eval::BasicEvaluatedExpression, + visitors::{create_traceable_error, JavascriptParser, Statement}, +}; + +fn is_reserved_param(pat: &Pat) -> bool { + const RESERVED_NAMES: [&str; 3] = ["require", "module", "exports"]; + pat + .as_ident() + .is_some_and(|ident| RESERVED_NAMES.contains(&ident.id.sym.as_str())) +} + +pub struct AMDRequireDependenciesBlockParserPlugin; + +impl JavascriptParserPlugin for AMDRequireDependenciesBlockParserPlugin { + fn call( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + for_name: &str, + ) -> Option { + if for_name == "require" { + self.process_call_require(parser, call_expr) + } else { + None + } + } +} + +impl AMDRequireDependenciesBlockParserPlugin { + fn process_array( + &self, + parser: &mut JavascriptParser, + block_deps: &mut Vec, + call_expr: &CallExpr, + param: &BasicEvaluatedExpression, + ) -> Option { + if param.is_array() { + for item in param.items().iter() { + let result = self.process_item(parser, block_deps, call_expr, item); + if result.is_none() { + self.process_context(parser, call_expr, item); + } + } + return Some(true); + } + None + } + + fn process_item( + &self, + parser: &mut JavascriptParser, + block_deps: &mut Vec, + call_expr: &CallExpr, + param: &BasicEvaluatedExpression, + ) -> Option { + if param.is_conditional() { + let options = param.options(); + + for option in options.iter() { + let result = self.process_item(parser, block_deps, call_expr, option); + if result.is_none() { + self.process_context(parser, call_expr, param); + } + } + + return Some(true); + } else if param.is_string() { + let param_str = param.string(); + let range = { + let (l, h) = param.range(); + (l, h - 1) + }; + + if param_str == "require" { + let dep = Box::new(ConstDependency::new( + range.0, + range.1, + RuntimeGlobals::REQUIRE.name().into(), + Some(RuntimeGlobals::REQUIRE), + )); + parser.presentational_dependencies.push(dep); + } else if param_str == "module" { + let dep = Box::new(ConstDependency::new( + range.0, + range.1, + "module".into(), + Some(RuntimeGlobals::MODULE), + )); + parser.presentational_dependencies.push(dep); + } else if param_str == "exports" { + let dep = Box::new(ConstDependency::new( + range.0, + range.1, + "exports".into(), + Some(RuntimeGlobals::EXPORTS), + )); + parser.presentational_dependencies.push(dep); + } else if let Some(local_module) = parser.get_local_module_mut(param_str) { + local_module.flag_used(); + let dep = Box::new(LocalModuleDependency::new( + local_module.clone(), + Some((range.0, range.1)), + false, + )); + parser.presentational_dependencies.push(dep); + return Some(true); + } else { + let mut dep = Box::new(AMDRequireItemDependency::new( + Atom::new(param_str.as_str()), + range, + )); + dep.set_optional(parser.in_try); + block_deps.push(dep); + } + + return Some(true); + } + None + } + + fn process_context( + &self, + _parser: &mut JavascriptParser, + _call_expr: &CallExpr, + _param: &BasicEvaluatedExpression, + ) -> Option { + // TODO: support amd context dep + None + } + + fn process_array_for_request_string(&self, param: &BasicEvaluatedExpression) -> Option { + if param.is_array() { + let mut result = param + .items() + .iter() + .map(|item| self.process_item_for_request_string(item)); + if result.all(|item| item.is_some()) { + return Some(result.map(|item| item.expect("")).join(" ")); + } + } + None + } + + #[allow(clippy::only_used_in_recursion)] + fn process_item_for_request_string(&self, param: &BasicEvaluatedExpression) -> Option { + if param.is_conditional() { + let mut result = param + .options() + .iter() + .map(|item| self.process_item_for_request_string(item)); + if result.all(|item| item.is_some()) { + return Some(result.map(|item| item.expect("")).join("|")); + } + } else if param.is_string() { + return Some(param.string().to_string()); + } + None + } + + fn process_function_argument( + &self, + parser: &mut JavascriptParser, + func_arg: &ExprOrSpread, + ) -> bool { + let mut bind_this = true; + + if let Some(func_expr) = func_arg.expr.get_function_expr() { + match func_expr.func { + Either::Left(func) => { + if let Some(body) = &func.function.body { + let params = func + .function + .params + .iter() + .filter(|param| !is_reserved_param(¶m.pat)) + .map(|param| Cow::Borrowed(¶m.pat)); + parser.in_function_scope(true, params, |parser| { + parser.walk_statement(Statement::Block(body)); + }); + } + } + Either::Right(arrow) => { + let params = arrow + .params + .iter() + .filter(|param| !is_reserved_param(param)) + .map(Cow::Borrowed); + parser.in_function_scope(true, params, |parser| match &*arrow.body { + BlockStmtOrExpr::BlockStmt(body) => parser.walk_statement(Statement::Block(body)), + BlockStmtOrExpr::Expr(expr) => parser.walk_expression(expr), + }); + } + } + + if let Some(bind_expr) = func_expr.expressions { + parser.walk_expression(bind_expr); + } + + if func_expr._need_this.is_some_and(|x| !x) { + bind_this = false; + } + } else { + parser.walk_expression(&func_arg.expr); + } + + bind_this + } + + fn process_call_require( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + ) -> Option { + if call_expr.args.is_empty() { + return None; + } + // TODO: check if args includes spread + + // require(['dep1', 'dep2'], callback, errorCallback); + + let first_arg = call_expr.args.first().expect("first arg cannot be None"); + let callback_arg = call_expr.args.get(1); + let error_callback_arg = call_expr.args.get(2); + + let param = parser.evaluate_expression(&first_arg.expr); + + let mut dep = Box::new(AMDRequireDependency::new( + (call_expr.span.real_lo(), call_expr.span.real_hi()), + Some(( + first_arg.expr.span().real_lo(), + first_arg.expr.span().real_hi(), + )), + callback_arg.map(|arg| (arg.expr.span().real_lo(), arg.expr.span().real_hi())), + error_callback_arg.map(|arg| (arg.expr.span().real_lo(), arg.expr.span().real_hi())), + )); + + let block_loc = Some(DependencyLocation::Real( + Into::::into(call_expr.span).with_source(parser.source_map.clone()), + )); + + if call_expr.args.len() == 1 { + let mut block_deps: Vec = vec![dep]; + let mut result = None; + parser.in_function_scope(true, iter::empty(), |parser| { + result = self.process_array(parser, &mut block_deps, call_expr, ¶m); + }); + if result.is_some_and(|x| x) { + let dep_block = Box::new(AsyncDependenciesBlock::new( + *parser.module_identifier, + block_loc, + None, + block_deps, + self.process_array_for_request_string(¶m), + )); + parser.blocks.push(dep_block); + return Some(true); + } else { + return None; + } + } + + if call_expr.args.len() == 2 || call_expr.args.len() == 3 { + let mut block_deps: Vec = vec![]; + + let mut result = None; + parser.in_function_scope(true, iter::empty(), |parser| { + result = self.process_array(parser, &mut block_deps, call_expr, ¶m) + }); + + if !result.is_some_and(|x| x) { + let dep = Box::new(UnsupportedDependency::new( + "unsupported".into(), + (call_expr.span.real_lo(), call_expr.span.real_hi()), + )); + parser.presentational_dependencies.push(dep); + parser.warning_diagnostics.push(Box::new( + create_traceable_error( + "UnsupportedFeatureWarning".into(), + "Cannot statically analyse 'require(…, …)'".into(), + parser.source_file, + call_expr.span.into(), + ) + .with_severity(Severity::Warning) + .with_hide_stack(Some(true)), + )); + return Some(true); + } + + dep.function_bind_this = + self.process_function_argument(parser, callback_arg.expect("2nd arg cannot be None")); + + if let Some(error_callback_arg) = error_callback_arg { + dep.error_callback_bind_this = self.process_function_argument(parser, error_callback_arg); + } + + block_deps.insert(0, dep); + let dep_block = Box::new(AsyncDependenciesBlock::new( + *parser.module_identifier, + block_loc, + None, + block_deps, + self.process_array_for_request_string(¶m), + )); + parser.blocks.push(dep_block); + + return Some(true); + } + + None + } +} diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs b/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs index 9e01a7894d6..a09ed097b45 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/mod.rs @@ -27,10 +27,15 @@ mod use_strict_plugin; mod webpack_included_plugin; mod worker_plugin; +pub mod amd_define_dependency_parser_plugin; +pub mod amd_require_dependencies_block_parser_plugin; pub mod define_plugin; pub mod hot_module_replacement_plugin; pub mod provide_plugin; +pub mod require_js_stuff_plugin; +pub(crate) use self::amd_define_dependency_parser_plugin::AMDDefineDependencyParserPlugin; +pub(crate) use self::amd_require_dependencies_block_parser_plugin::AMDRequireDependenciesBlockParserPlugin; 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; @@ -55,6 +60,7 @@ pub(crate) use self::r#const::{is_logic_op, ConstPlugin}; pub use self::r#trait::{BoxJavascriptParserPlugin, JavascriptParserPlugin}; pub(crate) use self::require_context_dependency_parser_plugin::RequireContextDependencyParserPlugin; pub(crate) use self::require_ensure_dependencies_block_parse_plugin::RequireEnsureDependenciesBlockParserPlugin; +pub(crate) use self::require_js_stuff_plugin::RequireJsStuffPlugin; pub(crate) use self::url_plugin::URLPlugin; pub(crate) use self::use_strict_plugin::UseStrictPlugin; pub(crate) use self::webpack_included_plugin::WebpackIsIncludedPlugin; diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/require_ensure_dependencies_block_parse_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/require_ensure_dependencies_block_parse_plugin.rs index 3bbe6fd1786..93bfc207d57 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/require_ensure_dependencies_block_parse_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/require_ensure_dependencies_block_parse_plugin.rs @@ -178,14 +178,14 @@ impl JavascriptParserPlugin for RequireEnsureDependenciesBlockParserPlugin { } } -struct FunctionExpression<'a> { - func: Either<&'a FnExpr, &'a ArrowExpr>, - expressions: Option<&'a Expr>, +pub(crate) struct FunctionExpression<'a> { + pub(crate) func: Either<&'a FnExpr, &'a ArrowExpr>, + pub(crate) expressions: Option<&'a Expr>, // Used by AMD - _need_this: Option, + pub(crate) _need_this: Option, } -trait GetFunctionExpression { +pub(crate) trait GetFunctionExpression { fn get_function_expr(&self) -> Option; fn inner_paren(&self) -> &Self; } diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/require_js_stuff_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/require_js_stuff_plugin.rs new file mode 100644 index 00000000000..5269f9116b3 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/parser_plugin/require_js_stuff_plugin.rs @@ -0,0 +1,198 @@ +use rspack_core::{ConstDependency, RuntimeGlobals, SpanExt}; +use swc_core::ecma::ast::{CallExpr, Expr, MemberExpr}; +use swc_core::{common::Spanned, ecma::ast::UnaryExpr}; + +use super::JavascriptParserPlugin; +use crate::utils::eval::{evaluate_to_identifier, evaluate_to_string, BasicEvaluatedExpression}; +use crate::visitors::{expr_matcher, JavascriptParser}; + +pub struct RequireJsStuffPlugin; + +const DEFINE: &str = "define"; +const REQUIRE: &str = "require"; +const DEFINE_AMD: &str = "define.amd"; +const REQUIRE_AMD: &str = "require.amd"; + +impl JavascriptParserPlugin for RequireJsStuffPlugin { + fn call( + &self, + parser: &mut JavascriptParser, + call_expr: &CallExpr, + for_name: &str, + ) -> Option { + if for_name == "require.config" || for_name == "requirejs.config" { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + call_expr.span.real_lo(), + call_expr.span.real_hi(), + "undefined".into(), + None, + ))); + Some(true) + } else { + None + } + } + + fn member( + &self, + parser: &mut JavascriptParser, + expr: &MemberExpr, + _for_name: &str, + ) -> Option { + if expr_matcher::is_require_version(expr) { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span.real_lo(), + expr.span.real_hi(), + "\"0.0.0\"".into(), + None, + ))); + return Some(true); + } + + if expr_matcher::is_require_onerror(expr) || expr_matcher::is_requirejs_onerror(expr) { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span.real_lo(), + expr.span.real_hi(), + RuntimeGlobals::UNCAUGHT_ERROR_HANDLER.name().into(), + Some(RuntimeGlobals::UNCAUGHT_ERROR_HANDLER), + ))); + return Some(true); + } + + // AMDPlugin + if expr_matcher::is_define_amd(expr) || expr_matcher::is_require_amd(expr) { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span.real_lo(), + expr.span.real_hi(), + RuntimeGlobals::AMD_OPTIONS.name().into(), + Some(RuntimeGlobals::AMD_OPTIONS), + ))); + return Some(true); + } + + None + } + + // The following is the logic from AMDPlugin, which mainly applies + // AMDDefineDependencyParserPlugin and AMDRequireDependenciesBlockParserPlugin. + // It also has some require.js related logic. I moved the logic here + // to avoid creating a `AMDPlugin` with just a few lines of code. + + fn r#typeof( + &self, + parser: &mut JavascriptParser, + expr: &UnaryExpr, + for_name: &str, + ) -> Option { + if for_name == DEFINE || for_name == REQUIRE { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span.real_lo(), + expr.span.real_hi(), + "\"function\"".into(), + None, + ))); + return Some(true); + } + + if for_name == DEFINE_AMD || for_name == REQUIRE_AMD { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span.real_lo(), + expr.span.real_hi(), + "\"object\"".into(), + None, + ))); + return Some(true); + } + + None + } + + fn evaluate_typeof( + &self, + _parser: &mut JavascriptParser, + expr: &UnaryExpr, + for_name: &str, + ) -> Option { + if for_name == DEFINE || for_name == REQUIRE { + return Some(evaluate_to_string( + "function".to_string(), + expr.span.real_lo(), + expr.span.real_hi(), + )); + } + + if for_name == DEFINE_AMD || for_name == REQUIRE_AMD { + return Some(evaluate_to_string( + "object".to_string(), + expr.span.real_lo(), + expr.span.real_hi(), + )); + } + + None + } + + fn evaluate_identifier( + &self, + _parser: &mut JavascriptParser, + ident: &str, + start: u32, + end: u32, + ) -> Option { + if ident == DEFINE_AMD { + return Some(evaluate_to_identifier( + ident.to_string(), + "define".to_string(), + Some(true), + start, + end, + )); + } + + if ident == REQUIRE_AMD { + return Some(evaluate_to_identifier( + ident.to_string(), + "require".to_string(), + Some(true), + start, + end, + )); + } + + None + } + + fn can_rename(&self, _parser: &mut JavascriptParser, for_name: &str) -> Option { + if for_name == DEFINE { + return Some(true); + } + None + } + + fn rename(&self, parser: &mut JavascriptParser, expr: &Expr, for_name: &str) -> Option { + if for_name == DEFINE { + parser + .presentational_dependencies + .push(Box::new(ConstDependency::new( + expr.span().real_lo(), + expr.span().real_hi(), + RuntimeGlobals::AMD_DEFINE.name().into(), + Some(RuntimeGlobals::AMD_DEFINE), + ))); + return Some(true); + } + None + } +} diff --git a/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs index 8b41e3d0a0f..ed4151cd7e9 100644 --- a/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/impl_plugin_for_js_plugin.rs @@ -66,6 +66,11 @@ async fn compilation( DependencyType::RequireResolve, params.normal_module_factory.clone(), ); + // AMDPlugin + compilation.set_dependency_factory( + DependencyType::AmdRequireItem, + params.normal_module_factory.clone(), + ); // RequireContextPlugin compilation.set_dependency_factory( DependencyType::RequireContext, 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 7e8cf0616f8..9e63d641c26 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -29,6 +29,7 @@ use swc_core::ecma::ast::{ use swc_core::ecma::ast::{Expr, Ident, Lit, MemberExpr, RestPat}; use swc_core::ecma::utils::ExprFactory; +use crate::dependency::local_module::LocalModule; use crate::parser_plugin::InnerGraphState; use crate::parser_plugin::{self, JavaScriptParserPluginDrive, JavascriptParserPlugin}; use crate::utils::eval::{self, BasicEvaluatedExpression}; @@ -240,6 +241,7 @@ pub struct JavascriptParser<'parser> { pub(crate) statement_path: Vec, pub(crate) prev_statement: Option, pub(crate) current_tag_info: Option, + pub(crate) local_modules: Vec, // ===== scope info ======= pub(crate) in_try: bool, pub(crate) in_short_hand: bool, @@ -308,6 +310,14 @@ impl<'parser> JavascriptParser<'parser> { plugins.push(Box::new(parser_plugin::ESMExportDependencyParserPlugin)); } + if compiler_options.amd.is_some() && (module_type.is_js_auto() || module_type.is_js_dynamic()) { + plugins.push(Box::new( + parser_plugin::AMDRequireDependenciesBlockParserPlugin, + )); + plugins.push(Box::new(parser_plugin::AMDDefineDependencyParserPlugin)); + plugins.push(Box::new(parser_plugin::RequireJsStuffPlugin)); + } + if module_type.is_js_auto() || module_type.is_js_dynamic() { plugins.push(Box::new(parser_plugin::CommonJsImportsParserPlugin)); plugins.push(Box::new(parser_plugin::CommonJsPlugin)); @@ -388,9 +398,29 @@ impl<'parser> JavascriptParser<'parser> { inner_graph: InnerGraphState::new(), additional_data, parse_meta, + local_modules: Default::default(), } } + pub fn add_local_module(&mut self, name: &str) -> LocalModule { + let m = LocalModule::new(name.into(), self.local_modules.len()); + self.local_modules.push(m.clone()); + m + } + + pub fn get_local_module(&self, name: &str) -> Option { + for m in self.local_modules.iter() { + if m.get_name() == name { + return Some(m.clone()); + } + } + None + } + + pub fn get_local_module_mut(&mut self, name: &str) -> Option<&mut LocalModule> { + self.local_modules.iter_mut().find(|m| m.get_name() == name) + } + pub fn is_asi_position(&self, pos: BytePos) -> bool { let curr_path = self.statement_path.last().expect("Should in statement"); if curr_path.span_hi() == pos && self.semicolons.contains(&pos) { @@ -482,7 +512,7 @@ impl<'parser> JavascriptParser<'parser> { self.definitions_db.set(definitions, name, info); } - fn set_variable(&mut self, name: String, variable: String) { + pub fn set_variable(&mut self, name: String, variable: String) { let id = self.definitions; if name == variable { self.definitions_db.delete(id, &name); @@ -881,7 +911,7 @@ impl<'parser> JavascriptParser<'parser> { current_scope.is_strict = value; } - fn detect_mode(&mut self, stmts: &[Stmt]) { + pub fn detect_mode(&mut self, stmts: &[Stmt]) { let Some(Lit::Str(str)) = stmts .first() .and_then(|stmt| stmt.as_expr()) 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 9666c8a170b..8648a19b30e 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/walk.rs @@ -78,7 +78,7 @@ impl<'parser> JavascriptParser<'parser> { self.in_tagged_template_tag = old_in_tagged_template_tag; } - fn in_function_scope<'a, I, F>(&mut self, has_this: bool, params: I, f: F) + pub(crate) fn in_function_scope<'a, I, F>(&mut self, has_this: bool, params: I, f: F) where F: FnOnce(&mut Self), I: Iterator>, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs index 631c81d7a23..4bef9c420e8 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/util.rs @@ -197,13 +197,15 @@ pub(crate) mod expr_matcher { is_webpack_module_id: "__webpack_module__.id", is_object_define_property: "Object.defineProperty", is_require_ensure: "require.ensure", + is_require_version: "require.version", + is_require_amd: "require.amd", + is_require_onerror: "require.onError", + is_requirejs_onerror: "requirejs.onError", + is_define_amd: "define.amd", // unsupported is_require_extensions: "require.extensions", is_require_config: "require.config", - is_require_version: "require.version", - is_require_amd: "require.amd", is_require_include: "require.include", - is_require_onerror: "require.onError", is_require_main_require: "require.main.require", is_module_parent_require: "module.parent.require", }); diff --git a/crates/rspack_plugin_runtime/src/runtime_module/amd_define.rs b/crates/rspack_plugin_runtime/src/runtime_module/amd_define.rs new file mode 100644 index 00000000000..5efcb08577f --- /dev/null +++ b/crates/rspack_plugin_runtime/src/runtime_module/amd_define.rs @@ -0,0 +1,34 @@ +use rspack_collections::Identifier; +use rspack_core::{ + impl_runtime_module, + rspack_sources::{BoxSource, RawSource, SourceExt}, + Compilation, RuntimeGlobals, RuntimeModule, +}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct AmdDefineRuntimeModule { + id: Identifier, +} + +impl Default for AmdDefineRuntimeModule { + fn default() -> Self { + Self::with_default(Identifier::from("webpack/runtime/amd_define")) + } +} + +impl RuntimeModule for AmdDefineRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, _compilation: &Compilation) -> rspack_error::Result { + Ok( + RawSource::from(format!( + "{} = function () {{ throw new Error('define cannot be used indirect'); }}", + RuntimeGlobals::AMD_DEFINE.name() + )) + .boxed(), + ) + } +} diff --git a/crates/rspack_plugin_runtime/src/runtime_module/amd_options.rs b/crates/rspack_plugin_runtime/src/runtime_module/amd_options.rs new file mode 100644 index 00000000000..307c7269e2d --- /dev/null +++ b/crates/rspack_plugin_runtime/src/runtime_module/amd_options.rs @@ -0,0 +1,36 @@ +use rspack_collections::Identifier; +use rspack_core::{ + impl_runtime_module, + rspack_sources::{BoxSource, RawSource, SourceExt}, + Compilation, RuntimeGlobals, RuntimeModule, +}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct AmdOptionsRuntimeModule { + id: Identifier, + options: String, +} + +impl AmdOptionsRuntimeModule { + pub fn new(options: String) -> Self { + Self::with_default(Identifier::from("webpack/runtime/amd_options"), options) + } +} + +impl RuntimeModule for AmdOptionsRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, _compilation: &Compilation) -> rspack_error::Result { + Ok( + RawSource::from(format!( + "{} = {}", + RuntimeGlobals::AMD_OPTIONS.name(), + self.options + )) + .boxed(), + ) + } +} diff --git a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs index 85cef363fb3..5d47ae01dce 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs +++ b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs @@ -1,3 +1,5 @@ +mod amd_define; +mod amd_options; mod async_module; mod auto_public_path; mod base_uri; @@ -40,6 +42,8 @@ mod startup_chunk_dependencies; mod startup_entry_point; mod system_context; mod utils; +pub use amd_define::AmdDefineRuntimeModule; +pub use amd_options::AmdOptionsRuntimeModule; pub use async_module::AsyncRuntimeModule; pub use auto_public_path::AutoPublicPathRuntimeModule; pub use base_uri::BaseUriRuntimeModule; diff --git a/crates/rspack_plugin_runtime/src/runtime_plugin.rs b/crates/rspack_plugin_runtime/src/runtime_plugin.rs index 27360ba6074..48727cd9ea4 100644 --- a/crates/rspack_plugin_runtime/src/runtime_plugin.rs +++ b/crates/rspack_plugin_runtime/src/runtime_plugin.rs @@ -16,17 +16,17 @@ use rspack_hook::{plugin, plugin_hook}; use rspack_plugin_javascript::{JavascriptModulesChunkHash, JsPlugin}; use crate::runtime_module::{ - chunk_has_css, chunk_has_js, is_enabled_for_chunk, AsyncRuntimeModule, - AutoPublicPathRuntimeModule, BaseUriRuntimeModule, ChunkNameRuntimeModule, - ChunkPrefetchPreloadFunctionRuntimeModule, CompatGetDefaultExportRuntimeModule, - CreateFakeNamespaceObjectRuntimeModule, CreateScriptRuntimeModule, CreateScriptUrlRuntimeModule, - DefinePropertyGettersRuntimeModule, ESMModuleDecoratorRuntimeModule, EnsureChunkRuntimeModule, - GetChunkFilenameRuntimeModule, GetChunkUpdateFilenameRuntimeModule, GetFullHashRuntimeModule, - GetMainFilenameRuntimeModule, GetTrustedTypesPolicyRuntimeModule, GlobalRuntimeModule, - HasOwnPropertyRuntimeModule, LoadScriptRuntimeModule, MakeNamespaceObjectRuntimeModule, - NodeModuleDecoratorRuntimeModule, NonceRuntimeModule, OnChunkLoadedRuntimeModule, - PublicPathRuntimeModule, RelativeUrlRuntimeModule, RuntimeIdRuntimeModule, - SystemContextRuntimeModule, + chunk_has_css, chunk_has_js, is_enabled_for_chunk, AmdDefineRuntimeModule, + AmdOptionsRuntimeModule, AsyncRuntimeModule, AutoPublicPathRuntimeModule, BaseUriRuntimeModule, + ChunkNameRuntimeModule, ChunkPrefetchPreloadFunctionRuntimeModule, + CompatGetDefaultExportRuntimeModule, CreateFakeNamespaceObjectRuntimeModule, + CreateScriptRuntimeModule, CreateScriptUrlRuntimeModule, DefinePropertyGettersRuntimeModule, + ESMModuleDecoratorRuntimeModule, EnsureChunkRuntimeModule, GetChunkFilenameRuntimeModule, + GetChunkUpdateFilenameRuntimeModule, GetFullHashRuntimeModule, GetMainFilenameRuntimeModule, + GetTrustedTypesPolicyRuntimeModule, GlobalRuntimeModule, HasOwnPropertyRuntimeModule, + LoadScriptRuntimeModule, MakeNamespaceObjectRuntimeModule, NodeModuleDecoratorRuntimeModule, + NonceRuntimeModule, OnChunkLoadedRuntimeModule, PublicPathRuntimeModule, + RelativeUrlRuntimeModule, RuntimeIdRuntimeModule, SystemContextRuntimeModule, }; static GLOBALS_ON_REQUIRE: LazyLock> = LazyLock::new(|| { @@ -77,6 +77,11 @@ static MODULE_DEPENDENCIES: LazyLock)>> RuntimeGlobals::NODE_MODULE_DECORATOR, vec![RuntimeGlobals::MODULE, RuntimeGlobals::REQUIRE_SCOPE], ), + (RuntimeGlobals::AMD_DEFINE, vec![RuntimeGlobals::REQUIRE]), + ( + RuntimeGlobals::AMD_OPTIONS, + vec![RuntimeGlobals::REQUIRE_SCOPE], + ), ] }); @@ -495,6 +500,19 @@ fn runtime_requirements_in_tree( .boxed(), )?; } + RuntimeGlobals::AMD_DEFINE => { + if compilation.options.amd.is_some() { + compilation.add_runtime_module(chunk_ukey, AmdDefineRuntimeModule::default().boxed())?; + } + } + RuntimeGlobals::AMD_OPTIONS => { + if let Some(options) = &compilation.options.amd { + compilation.add_runtime_module( + chunk_ukey, + AmdOptionsRuntimeModule::new(options.clone()).boxed(), + )?; + } + } _ => {} } } diff --git a/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap index fe1c22cb984..2b3cc1bc741 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap @@ -2,6 +2,7 @@ exports[`Base Defaults Snapshot should have the correct base config 1`] = ` Object { + amd: undefined, bail: false, cache: false, context: , diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-function/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-function/index.js new file mode 100644 index 00000000000..84b9ef74d94 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-function/index.js @@ -0,0 +1,8 @@ +import * as lib from "./lib"; + +it("define(function () {...}) should work well", function () { + expect(lib.a).toBe(1); + expect(lib.b).toBe(2); + expect(typeof lib.add).toBe('function'); + expect(lib.add(1, 2)).toBe(3); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-function/lib.js b/packages/rspack-test-tools/tests/configCases/amd/define-function/lib.js new file mode 100644 index 00000000000..8cf2064b0d1 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-function/lib.js @@ -0,0 +1,9 @@ +define(function () { + return { + a: 1, + b: 2, + add: function (a, b) { + return a + b; + }, + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-function/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-function/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-function/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-object/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-object/index.js new file mode 100644 index 00000000000..7fbf41a5bba --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-object/index.js @@ -0,0 +1,8 @@ +import * as lib from "./lib"; + +it("define({...}) should work well", function () { + expect(lib.a).toBe(1); + expect(lib.b).toBe(2); + expect(typeof lib.add).toBe('function'); + expect(lib.add(1, 2)).toBe(3); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-object/lib.js b/packages/rspack-test-tools/tests/configCases/amd/define-object/lib.js new file mode 100644 index 00000000000..730c03fc9db --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-object/lib.js @@ -0,0 +1,7 @@ +define({ + a: 1, + b: 2, + add: function (a, b) { + return a + b; + }, +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-object/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-object/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-object/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-others/hello.js b/packages/rspack-test-tools/tests/configCases/amd/define-others/hello.js new file mode 100644 index 00000000000..f931dab4f89 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-others/hello.js @@ -0,0 +1 @@ +define('hello'); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-others/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-others/index.js new file mode 100644 index 00000000000..e51493747af --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-others/index.js @@ -0,0 +1,7 @@ +import hello from "./hello"; +import luckyNumber from './lucky-number'; + +it("should be able to define any JS values", function () { + expect(hello).toBe('hello'); + expect(luckyNumber).toBe(7); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-others/lucky-number.js b/packages/rspack-test-tools/tests/configCases/amd/define-others/lucky-number.js new file mode 100644 index 00000000000..c1d467d9439 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-others/lucky-number.js @@ -0,0 +1 @@ +define(7); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-others/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-others/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-others/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/add.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/add.js new file mode 100644 index 00000000000..1ea21ee7a3e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/add.js @@ -0,0 +1,3 @@ +define(function () { + return (a, b) => a + b; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/constants.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/constants.js new file mode 100644 index 00000000000..394afc468ef --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/constants.js @@ -0,0 +1,4 @@ +define({ + FOO: 'foo', + BAR: 'bar', +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/hello.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/hello.js new file mode 100644 index 00000000000..88fba041b3b --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/hello.js @@ -0,0 +1,5 @@ +define(function () { + return (name) => { + return `Hello, ${name}`; + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/index.js new file mode 100644 index 00000000000..6268b687544 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/index.js @@ -0,0 +1,9 @@ +import * as lib from './lib'; + +it("define([...], function () {...}) should work well", function () { + expect(lib.FOO).toBe('foo'); + expect(typeof lib.add).toBe('function'); + expect(lib.add(1, 2)).toBe(3); + expect(typeof lib.hello).toBe('function'); + expect(lib.hello('foo')).toBe('Hello, foo'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/lib.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/lib.js new file mode 100644 index 00000000000..5ab39fcfdd6 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/lib.js @@ -0,0 +1,8 @@ +define(['./add', './constants', './hello'], function (add, constants, hello) { + + return { + add, + FOO: constants.FOO, + hello, + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-deps/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-name/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/index.js new file mode 100644 index 00000000000..f2a8c6f7e38 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/index.js @@ -0,0 +1,13 @@ +import * as lib from "./lib"; + +import jQuery from './jquery'; + +it("should be able to define a local module with a name", function () { + expect(lib.foo).toBe('foo'); + expect(typeof lib.add).toBe('function'); + expect(lib.add(1, 2)).toBe(3); +}); + +it('should export last unused local module', function () { + expect(jQuery).toBe('jQuery'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-name/jquery.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/jquery.js new file mode 100644 index 00000000000..3325879dc1f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/jquery.js @@ -0,0 +1,12 @@ + +define('foo', 'foo'); + +define('add', function () { + return function add(a, b) { + return a + b; + }; +}); + +define('jQuery', [], function () { + return 'jQuery'; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-name/lib.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/lib.js new file mode 100644 index 00000000000..6802fd13faf --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/lib.js @@ -0,0 +1,16 @@ +define('foo', 'foo'); +define('bar', 'bar'); + +define('add', function () { + return (a, b) => a + b; +}); + +define('hello', function () { + return function (name) { + console.log(`Hello, ${name}`); + }; +}); + +define(['foo', 'add'], function (foo, add) { + return { foo, add }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-with-name/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-with-name/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/add.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/add.js new file mode 100644 index 00000000000..3ad0c9ace43 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/add.js @@ -0,0 +1,5 @@ +define(function () { + return function add(a, b) { + return a + b; + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/constants.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/constants.js new file mode 100644 index 00000000000..79a6d113a48 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/constants.js @@ -0,0 +1,2 @@ +exports.foo = 'foo'; +exports.bar = 'bar'; diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/hello.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/hello.js new file mode 100644 index 00000000000..81451e853bb --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/hello.js @@ -0,0 +1,4 @@ + +module.exports = function hello(name) { + return `Hello, ${name}`; +} diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/index.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/index.js new file mode 100644 index 00000000000..ab433c20742 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/index.js @@ -0,0 +1,24 @@ +import lib1 from "./lib1"; +import lib2 from './lib2'; + +it("define(function (require, module, exports) {}) should work well", function () { + expect(lib1.foo).toBe('foo'); + expect(lib1.bar).toBe('bar'); + + expect(typeof lib1.add).toBe('function'); + expect(lib1.add(1, 2)).toBe(3); + + expect(typeof lib1.hello).toBe('function'); + expect(lib1.hello('world')).toBe('Hello, world'); +}); + +it("should be able to specify require/module/exports in deps array", function () { + expect(lib2.foo).toBe('foo'); + expect(lib2.bar).toBe('bar'); + + expect(typeof lib2.add).toBe('function'); + expect(lib2.add(1, 2)).toBe(3); + + expect(typeof lib2.hello).toBe('function'); + expect(lib2.hello('world')).toBe('Hello, world'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib1.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib1.js new file mode 100644 index 00000000000..577cb2153fa --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib1.js @@ -0,0 +1,7 @@ +define(function (module, exports, require) { + const { foo, bar } = require('./constants'); + exports.foo = foo; + exports.bar = bar; + exports.add = require('./add'); + exports.hello = require('./hello'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib2.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib2.js new file mode 100644 index 00000000000..8190ae3a7ee --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/lib2.js @@ -0,0 +1,6 @@ +define(['./constants', 'exports', 'require'], function (constants, e, r) { + e.foo = constants.foo; + e.bar = constants.bar; + e.add = r('./add'); + e.hello = r('./hello'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/define-wrapper/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/index.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/index.js new file mode 100644 index 00000000000..1e64b088d77 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/index.js @@ -0,0 +1,22 @@ + +it('should work with jquery', function () { + const $ = require('./jquery'); + expect(typeof $).toBe('function'); + expect($()).toBe('hi jQuery'); + expect($.version).toBe('3.7.1'); +}); + +it('should work with jquery-ui', function () { + const ui = require('./jquery-ui'); + expect(ui).toStrictEqual({ version: '0.0.0' }); +}); + +it('should work with json-logic-js', function () { + const jsonLogic = require('./json-logic-js'); + expect(jsonLogic).toStrictEqual({ version: '0.0.0' }); +}); + +import webpackUmdOutput from './webpack-umd-output'; +it('should work with webpack umd output', function () { + expect(webpackUmdOutput).toStrictEqual({ version: '0.0.0' }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery-ui.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery-ui.js new file mode 100644 index 00000000000..3b530a52577 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery-ui.js @@ -0,0 +1,16 @@ +/*! + * Code simplified from jQuery UI 1.14.1 + * for testing amd support + */ +(function (factory) { + "use strict"; + if (typeof define === "function" && define.amd) { + // AMD. Register as an anonymous module. + define(["jquery"], factory); + } else { + // Browser globals + factory(jQuery); + } +})(function ($) { + return { version: '0.0.0' }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery.js new file mode 100644 index 00000000000..f265b99b612 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/jquery.js @@ -0,0 +1,34 @@ +/*! + * Code simplified from https://code.jquery.com/jquery-3.7.1.js + * for testing amd support + */ +(function (global, factory) { + "use strict"; + if (typeof module === "object" && typeof module.exports === "object") { + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket trac-14549 for more info. + // module.exports = global.document ? + // factory(global, true) : + // function (w) { + // if (!w.document) { + // throw new Error("jQuery requires a window with a document"); + // } + // return factory(w); + // }; + module.exports = factory(global, true); + } else { + factory(global); + } + + // Pass this if window is not defined yet +})(typeof window !== "undefined" ? window : this, function (window, noGlobal) { + var jQuery = function () { return 'hi jQuery' }; + jQuery.version = '3.7.1'; + window.jQuery = jQuery; + return jQuery; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/json-logic-js.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/json-logic-js.js new file mode 100644 index 00000000000..d7206bc8ba2 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/json-logic-js.js @@ -0,0 +1,16 @@ +/*! + * Code simplified from https://github.com/jwadhams/json-logic-js/blob/master/logic.js + * for testing amd support + */ +; (function (root, factory) { + if (typeof define === "function" && define.amd) { + define(factory); + } else if (typeof exports === "object") { + module.exports = factory(); + } else { + root.jsonLogic = factory(); + } +}(this, function () { + var jsonLogic = { version: '0.0.0' }; + return jsonLogic; +})); diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/rspack.config.js new file mode 100644 index 00000000000..ee99fde10be --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/rspack.config.js @@ -0,0 +1,8 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, + externals: { jquery: 'global $' }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/webpack-umd-output.js b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/webpack-umd-output.js new file mode 100644 index 00000000000..ffdab8bb955 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/real-amd-libs/webpack-umd-output.js @@ -0,0 +1,12 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if (typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if (typeof define === 'function' && define.amd) + define([], factory); + else { + var a = factory(); + for (var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i]; + } +})(this, () => { + return { version: '0.0.0' }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/add.js b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/add.js new file mode 100644 index 00000000000..4d0a976f120 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/add.js @@ -0,0 +1 @@ +throw 'some error'; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/index.js b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/index.js new file mode 100644 index 00000000000..7b2698d594c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/index.js @@ -0,0 +1,9 @@ + +it("amd require error callback should be called with the error", function (done) { + require(['./add'], function () { + done(new Error('success callback should not be called')); + }, function (error) { + expect(error).toBe('some error'); + done(); + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-error-callback/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-local-module/index.js b/packages/rspack-test-tools/tests/configCases/amd/require-local-module/index.js new file mode 100644 index 00000000000..c0ae0129aa6 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-local-module/index.js @@ -0,0 +1,13 @@ +define('add', function () { + return function add(a, b) { + return a + b; + }; +}); + +it('AMD require should support local module', function (done) { + require(['add'], function (add) { + expect(typeof add).toBe('function'); + expect(add(1, 2)).toBe(3); + done(); + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-local-module/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/require-local-module/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-local-module/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/index.js b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/index.js new file mode 100644 index 00000000000..7281fb0334d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/index.js @@ -0,0 +1,5 @@ +it('\'require(…, …)\' should throw an error if cannot be statically analysed', function () { + expect(function () { + require({}, function () { }); + }).toThrow(); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/warnings.js b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/warnings.js new file mode 100644 index 00000000000..626d58e8ff8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-unsupported/warnings.js @@ -0,0 +1,3 @@ +module.exports = [ + [/Cannot statically analyse/], +]; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/add.js b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/add.js new file mode 100644 index 00000000000..3ad0c9ace43 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/add.js @@ -0,0 +1,5 @@ +define(function () { + return function add(a, b) { + return a + b; + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/constants.js b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/constants.js new file mode 100644 index 00000000000..3b80e22dc14 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/constants.js @@ -0,0 +1,2 @@ + +export const HELLO = 'hello'; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/hello.js b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/hello.js new file mode 100644 index 00000000000..2e43a383976 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/hello.js @@ -0,0 +1,8 @@ + +define(function (require) { + const { HELLO } = require('./constants'); + + return function hello(name) { + return `${HELLO}, ${name}`; + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/index.js b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/index.js new file mode 100644 index 00000000000..a8a296c6d7c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/index.js @@ -0,0 +1,11 @@ +it('require([], function(require) {}) should work well', function (done) { + require(['./hello'], function (hello, require) { + expect(typeof hello).toBe('function'); + expect(hello('world')).toBe('hello, world'); + + const add = require('./add'); + expect(typeof add).toBe('function'); + expect(add(1, 2)).toBe(3); + done(); + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require-wrapper/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/require/add.js b/packages/rspack-test-tools/tests/configCases/amd/require/add.js new file mode 100644 index 00000000000..3ad0c9ace43 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require/add.js @@ -0,0 +1,5 @@ +define(function () { + return function add(a, b) { + return a + b; + }; +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require/index.js b/packages/rspack-test-tools/tests/configCases/amd/require/index.js new file mode 100644 index 00000000000..804ffa80632 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require/index.js @@ -0,0 +1,8 @@ + +it("require([...], function () {}) should work well", function (done) { + require(['./add'], function (add) { + expect(typeof add).toBe('function'); + expect(add(1, 2)).toBe(3); + done(); + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/require/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/require/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/require/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/index.js b/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/index.js new file mode 100644 index 00000000000..badd2b281c0 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/index.js @@ -0,0 +1,29 @@ +it('`require.config()` and `requirejs.config()` call should be replaced with undefined', function () { + expect(require.config()).toBeUndefined(); + expect(requirejs.config()).toBeUndefined(); +}); +it('require.version should be "0.0.0"', function () { + expect(require.version).toBe('0.0.0'); +}); + +const amdOptions = { jQuery: true }; + +it('`define.amd` should be equal to `options.amd`', function () { + expect(define.amd).toStrictEqual(amdOptions); + expect(typeof define.amd).toBe('object'); +}); + +it('`require.amd` should be equal to `options.amd`', function () { + expect(require.amd).toStrictEqual(amdOptions); + expect(typeof require.amd).toBe('object'); +}); + +it('define can be renamed, but calling renamed define will throw an error', function () { + const def = define; + expect(def).toThrow(); +}) + +it('`typeof define` and `typeof require` should be function', function () { + expect(typeof define).toBe('function'); + expect(typeof require).toBe('function'); +}); diff --git a/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/rspack.config.js b/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/rspack.config.js new file mode 100644 index 00000000000..dfb02e2ee7e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/amd/requirejs-stuffs/rspack.config.js @@ -0,0 +1,7 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + resolve: { + extensions: ["...", ".js"], + }, + amd: { jQuery: true }, +}; diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 6cd125acb54..2aa138b4475 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -88,6 +88,9 @@ type allKeys = T extends any ? keyof T : never; // @public (undocumented) type AllowTarget = "web" | "webworker" | "es3" | "es5" | "es2015" | "es2016" | "es2017" | "es2018" | "es2019" | "es2020" | "es2021" | "es2022" | "node" | "async-node" | `node${number}` | `async-node${number}` | `node${number}.${number}` | `async-node${number}.${number}` | "electron-main" | `electron${number}-main` | `electron${number}.${number}-main` | "electron-renderer" | `electron${number}-renderer` | `electron${number}.${number}-renderer` | "electron-preload" | `electron${number}-preload` | `electron${number}.${number}-preload` | "nwjs" | `nwjs${number}` | `nwjs${number}.${number}` | "node-webkit" | `node-webkit${number}` | `node-webkit${number}.${number}` | "browserslist" | `browserslist:${string}`; +// @public +export type Amd = false | Record; + // @public (undocumented) interface AmdConfig extends BaseModuleConfig { // (undocumented) @@ -5252,6 +5255,7 @@ declare namespace rspackExports { DevServer, IgnoreWarnings, Profile, + Amd, Bail, Performance_2 as Performance, RspackOptions, @@ -5298,6 +5302,7 @@ export type RspackOptions = { devServer?: DevServer; module?: ModuleOptions; profile?: Profile; + amd?: Amd; bail?: Bail; performance?: Performance_2; }; @@ -8241,6 +8246,7 @@ export const rspackOptions: z.ZodObject<{ noParse?: string | RegExp | ((args_0: string, ...args: unknown[]) => boolean) | (string | RegExp | ((args_0: string, ...args: unknown[]) => boolean))[] | undefined; }>>; profile: z.ZodOptional; + amd: z.ZodOptional, z.ZodRecord]>>; bail: z.ZodOptional; performance: z.ZodOptional, z.ZodBoolean>>; @@ -8507,6 +8513,7 @@ export const rspackOptions: z.ZodObject<{ __dirname?: boolean | "warn-mock" | "mock" | "eval-only" | "node-module" | undefined; __filename?: boolean | "warn-mock" | "mock" | "eval-only" | "node-module" | undefined; } | undefined; + amd?: false | Record | undefined; profile?: boolean | undefined; cache?: boolean | undefined; devtool?: false | "eval" | "cheap-source-map" | "cheap-module-source-map" | "source-map" | "inline-cheap-source-map" | "inline-cheap-module-source-map" | "inline-source-map" | "inline-nosources-cheap-source-map" | "inline-nosources-cheap-module-source-map" | "inline-nosources-source-map" | "nosources-cheap-source-map" | "nosources-cheap-module-source-map" | "nosources-source-map" | "hidden-nosources-cheap-source-map" | "hidden-nosources-cheap-module-source-map" | "hidden-nosources-source-map" | "hidden-cheap-source-map" | "hidden-cheap-module-source-map" | "hidden-source-map" | "eval-cheap-source-map" | "eval-cheap-module-source-map" | "eval-source-map" | "eval-nosources-cheap-source-map" | "eval-nosources-cheap-module-source-map" | "eval-nosources-source-map" | undefined; @@ -9129,6 +9136,7 @@ export const rspackOptions: z.ZodObject<{ __dirname?: boolean | "warn-mock" | "mock" | "eval-only" | "node-module" | undefined; __filename?: boolean | "warn-mock" | "mock" | "eval-only" | "node-module" | undefined; } | undefined; + amd?: false | Record | undefined; profile?: boolean | undefined; cache?: boolean | undefined; devtool?: false | "eval" | "cheap-source-map" | "cheap-module-source-map" | "source-map" | "inline-cheap-source-map" | "inline-cheap-module-source-map" | "inline-source-map" | "inline-nosources-cheap-source-map" | "inline-nosources-cheap-module-source-map" | "inline-nosources-source-map" | "nosources-cheap-source-map" | "nosources-cheap-module-source-map" | "nosources-source-map" | "hidden-nosources-cheap-source-map" | "hidden-nosources-cheap-module-source-map" | "hidden-nosources-source-map" | "hidden-cheap-source-map" | "hidden-cheap-module-source-map" | "hidden-source-map" | "eval-cheap-source-map" | "eval-cheap-module-source-map" | "eval-source-map" | "eval-nosources-cheap-source-map" | "eval-nosources-cheap-module-source-map" | "eval-nosources-source-map" | undefined; @@ -9514,6 +9522,8 @@ export { RspackOptionsApply as WebpackOptionsApply } // @public (undocumented) export interface RspackOptionsNormalized { + // (undocumented) + amd?: string; // (undocumented) bail?: Bail; // (undocumented) @@ -10493,6 +10503,7 @@ declare namespace t { DevServer, IgnoreWarnings, Profile, + Amd, Bail, Performance_2 as Performance, RspackOptions, diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index d503dfa0c39..34bfc4ad139 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -123,6 +123,7 @@ export const getRawOptions = ( node: getRawNode(options.node), // SAFETY: applied default value in `applyRspackOptionsDefaults`. profile: options.profile!, + amd: options.amd, // SAFETY: applied default value in `applyRspackOptionsDefaults`. bail: options.bail!, __references: {} diff --git a/packages/rspack/src/config/normalization.ts b/packages/rspack/src/config/normalization.ts index acadf3bffe0..21019f25543 100644 --- a/packages/rspack/src/config/normalization.ts +++ b/packages/rspack/src/config/normalization.ts @@ -333,6 +333,7 @@ export const getNormalizedRspackOptions = ( watchOptions: cloneObject(config.watchOptions), devServer: config.devServer, profile: config.profile, + amd: config.amd ? JSON.stringify(config.amd) : undefined, bail: config.bail }; }; @@ -589,5 +590,6 @@ export interface RspackOptionsNormalized { ignoreWarnings?: IgnoreWarningsNormalized; performance?: Performance; profile?: Profile; + amd?: string; bail?: Bail; } diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index f62dd9ca8df..8a5d87e59c5 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -2615,6 +2615,13 @@ export type IgnoreWarnings = ( export type Profile = boolean; //#endregion +//#region amd +/** + * Set the value of `require.amd` and `define.amd`. Or disable AMD support. + */ +export type Amd = false | Record; +//#endregion + //#region Bail /** * Fail out on the first error instead of tolerating it. @@ -2762,6 +2769,11 @@ export type RspackOptions = { * Whether to capture a profile of the application. */ profile?: Profile; + /** + * Set the value of `require.amd` or `define.amd`. + * Setting `amd` to false will disable rspack's AMD support. + */ + amd?: Amd; /** * Whether to fail on the first error. */ diff --git a/packages/rspack/src/config/zod.ts b/packages/rspack/src/config/zod.ts index e9ab79fddeb..3d1957ee139 100644 --- a/packages/rspack/src/config/zod.ts +++ b/packages/rspack/src/config/zod.ts @@ -1364,6 +1364,10 @@ const ignoreWarnings = z const profile = z.boolean() satisfies z.ZodType; //#endregion +//#region Amd +const amd = z.literal(false).or(z.record(z.any())) satisfies z.ZodType; +//#endregion + //#region Bail const bail = z.boolean() satisfies z.ZodType; //#endregion @@ -1408,6 +1412,7 @@ export const rspackOptions = z.strictObject({ devServer: devServer.optional(), module: moduleOptions.optional(), profile: profile.optional(), + amd: amd.optional(), bail: bail.optional(), performance: performance.optional() }) satisfies z.ZodType;