diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin.rs deleted file mode 100644 index 128e4c96693..00000000000 --- a/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::collections::HashMap; - -use once_cell::sync::OnceCell; -use rspack_core::{ - ApplyContext, Compilation, CompilationParams, CompilerCompilation, CompilerOptions, - ConstDependency, ModuleType, NormalModuleFactoryParser, ParserAndGenerator, ParserOptions, - Plugin, PluginContext, SpanExt, -}; -use rspack_error::{ - miette::{self, Diagnostic}, - thiserror::{self, Error}, - DiagnosticExt, Result, -}; -use rspack_hook::{plugin, plugin_hook}; -use swc_core::common::Spanned; - -use crate::{ - parser_and_generator::JavaScriptParserAndGenerator, parser_plugin::JavascriptParserPlugin, - visitors::JavascriptParser, BoxJavascriptParserPlugin, -}; - -type DefineValue = HashMap; - -#[plugin] -#[derive(Debug, Default, Clone)] -pub struct DefinePlugin { - definitions: DefineValue, - cached_names: OnceCell>, -} - -impl DefinePlugin { - pub fn new(definitions: DefineValue) -> Self { - Self::new_inner(definitions, OnceCell::new()) - } - - fn cached_names(&self) -> &Vec { - self.cached_names.get_or_init(|| { - let names = self.definitions.keys(); - names - .flat_map(|name| { - let splitted: Vec<&str> = name.split('.').collect(); - let mut val = if !splitted.is_empty() { - (0..splitted.len() - 1) - .map(|i| splitted[0..i + 1].join(".")) - .collect::>() - } else { - vec![] - }; - // !isTypeof - val.push(name.to_string()); - val - }) - .collect() - }) - } -} - -#[derive(Debug, Error, Diagnostic)] -#[error("DefinePlugin:\nConflicting values for '{0}' ('{1}' !== '{2}')")] -#[diagnostic(severity(Warning))] -struct ConflictingValuesError(String, String, String); - -#[plugin_hook(CompilerCompilation for DefinePlugin)] -async fn compilation( - &self, - compilation: &mut Compilation, - _params: &mut CompilationParams, -) -> Result<()> { - self.definitions.iter().for_each(|(key, value)| { - let name = format!("{VALUE_DEP_PREFIX}{key}"); - if let Some(prev) = compilation.value_cache_versions.get(&name) - && prev != value - { - compilation.push_diagnostic( - ConflictingValuesError(key.to_string(), prev.clone(), value.clone()) - .boxed() - .into(), - ); - } else { - compilation.value_cache_versions.insert(name, value.clone()); - } - }); - Ok(()) -} - -#[plugin_hook(NormalModuleFactoryParser for DefinePlugin)] -fn nmf_parser( - &self, - module_type: &ModuleType, - parser: &mut dyn ParserAndGenerator, - _parser_options: Option<&ParserOptions>, -) -> Result<()> { - if module_type.is_js_like() - && let Some(parser) = parser.downcast_mut::() - { - parser.add_parser_plugin(Box::new(self.clone()) as BoxJavascriptParserPlugin); - } - Ok(()) -} - -impl Plugin for DefinePlugin { - fn name(&self) -> &'static str { - "rspack.DefinePlugin" - } - - fn apply( - &self, - ctx: PluginContext<&mut ApplyContext>, - _options: &mut CompilerOptions, - ) -> Result<()> { - ctx - .context - .compiler_hooks - .compilation - .tap(compilation::new(self)); - ctx - .context - .normal_module_factory_hooks - .parser - .tap(nmf_parser::new(self)); - Ok(()) - } -} - -fn dep( - parser: &mut JavascriptParser, - for_name: &str, - definitions: &DefineValue, - start: u32, - end: u32, - asi_safe: bool, -) -> Option { - if let Some(value) = definitions.get(for_name) { - let code = if parser.in_short_hand { - format!("{for_name}: {value}") - } else if asi_safe { - format!("({value})") - } else { - format!(";({value})") - }; - - return Some(ConstDependency::new(start, end, code.into(), None)); - } - None -} - -const VALUE_DEP_PREFIX: &str = "webpack/DefinePlugin "; - -impl JavascriptParserPlugin for DefinePlugin { - fn can_rename(&self, _: &mut JavascriptParser, str: &str) -> Option { - let names = self.cached_names(); - names.iter().any(|l| l.eq(str)).then_some(true) - } - - fn evaluate_identifier( - &self, - parser: &mut JavascriptParser, - ident: &str, - start: u32, - end: u32, - ) -> Option { - if let Some(val) = self.definitions.get(ident) { - return parser - .evaluate(val.to_string(), "DefinePlugin".to_string()) - .map(|mut evaluated| { - evaluated.set_range(start, end); - evaluated - }); - } - None - } - - fn call( - &self, - parser: &mut JavascriptParser, - expr: &swc_core::ecma::ast::CallExpr, - for_name: &str, - ) -> Option { - dep( - parser, - for_name, - &self.definitions, - expr.callee.span().real_lo(), - expr.callee.span().real_hi(), - !parser.is_asi_position(expr.span_lo()), - ) - .map(|dep| { - parser.presentational_dependencies.push(Box::new(dep)); - // FIXME: webpack use `walk_expression` here - parser.walk_expr_or_spread(&expr.args); - true - }) - } - - fn member( - &self, - parser: &mut JavascriptParser, - expr: &swc_core::ecma::ast::MemberExpr, - for_name: &str, - ) -> Option { - dep( - parser, - for_name, - &self.definitions, - expr.span().real_lo(), - expr.span().real_hi(), - !parser.is_asi_position(expr.span_lo()), - ) - .map(|dep| { - parser.presentational_dependencies.push(Box::new(dep)); - true - }) - } - - fn identifier( - &self, - parser: &mut JavascriptParser, - ident: &swc_core::ecma::ast::Ident, - for_name: &str, - ) -> Option { - dep( - parser, - for_name, - &self.definitions, - ident.span.real_lo(), - ident.span.real_hi(), - !parser.is_asi_position(ident.span_lo()), - ) - .map(|dep| { - parser.presentational_dependencies.push(Box::new(dep)); - true - }) - } -} diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/mod.rs b/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/mod.rs new file mode 100644 index 00000000000..9574ff72519 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/mod.rs @@ -0,0 +1,124 @@ +mod parser; + +use std::{borrow::Cow, collections::HashMap}; + +use itertools::Itertools; +use parser::{walk_definitions, DefineParserPlugin}; +use rspack_core::{ + ApplyContext, Compilation, CompilationParams, CompilerCompilation, CompilerOptions, ModuleType, + NormalModuleFactoryParser, ParserAndGenerator, ParserOptions, Plugin, PluginContext, +}; +use rspack_error::{ + miette::{self, Diagnostic}, + thiserror::{self, Error}, + DiagnosticExt, Result, +}; +use rspack_hook::{plugin, plugin_hook}; +use serde_json::Value; + +use crate::parser_and_generator::JavaScriptParserAndGenerator; + +type DefineValue = HashMap; + +const VALUE_DEP_PREFIX: &str = "webpack/DefinePlugin "; + +#[plugin] +#[derive(Debug)] +pub struct DefinePlugin { + definitions: DefineValue, +} + +impl DefinePlugin { + pub fn new(definitions: DefineValue) -> Self { + Self::new_inner(definitions) + } +} + +#[derive(Debug, Error, Diagnostic)] +#[error("DefinePlugin:\nConflicting values for '{0}' ({1} !== {2})")] +#[diagnostic(severity(Warning))] +struct ConflictingValuesError(String, String, String); + +#[plugin_hook(CompilerCompilation for DefinePlugin)] +async fn compilation( + &self, + compilation: &mut Compilation, + _params: &mut CompilationParams, +) -> Result<()> { + fn walk_definitions<'d, 's>( + definitions: impl Iterator, + compilation: &mut Compilation, + prefix: Cow<'s, str>, + ) { + definitions.for_each(|(key, value)| { + let name = format!("{VALUE_DEP_PREFIX}{prefix}{key}"); + let value_str = value.to_string(); + if let Some(prev) = compilation.value_cache_versions.get(&name) + && !prev.eq(&value_str) + { + compilation.push_diagnostic( + ConflictingValuesError(format!("{prefix}{key}"), prev.clone(), value_str) + .boxed() + .into(), + ); + } else { + compilation.value_cache_versions.insert(name, value_str); + } + if let Some(value) = value.as_object() { + walk_definitions( + value.iter(), + compilation, + Cow::Owned(format!("{prefix}{key}.")), + ) + } else if let Some(value) = value.as_array() { + let indexes = (0..value.len()) + .map(|index| format!("{}", index)) + .collect_vec(); + let iter = indexes.iter().zip(value.iter()); + walk_definitions(iter, compilation, Cow::Owned(format!("{prefix}{key}."))) + } + }); + } + walk_definitions(self.definitions.iter(), compilation, "".into()); + Ok(()) +} + +#[plugin_hook(NormalModuleFactoryParser for DefinePlugin)] +fn nmf_parser( + &self, + module_type: &ModuleType, + parser: &mut dyn ParserAndGenerator, + _parser_options: Option<&ParserOptions>, +) -> Result<()> { + if module_type.is_js_like() + && let Some(parser) = parser.downcast_mut::() + { + let walk_data = walk_definitions(&self.definitions); + parser.add_parser_plugin(Box::new(DefineParserPlugin { walk_data })); + } + Ok(()) +} + +impl Plugin for DefinePlugin { + fn name(&self) -> &'static str { + "rspack.DefinePlugin" + } + + fn apply( + &self, + ctx: PluginContext<&mut ApplyContext>, + _options: &mut CompilerOptions, + ) -> Result<()> { + ctx + .context + .compiler_hooks + .compilation + .tap(compilation::new(self)); + ctx + .context + .normal_module_factory_hooks + .parser + .tap(nmf_parser::new(self)); + Ok(()) + } +} diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/parser.rs b/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/parser.rs new file mode 100644 index 00000000000..56107a852e5 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/parser_plugin/define_plugin/parser.rs @@ -0,0 +1,584 @@ +use std::{borrow::Cow, sync::Arc}; + +use itertools::Itertools as _; +use once_cell::sync::Lazy; +use regex::Regex; +use rspack_core::{ConstDependency, RuntimeGlobals, SpanExt as _}; +use rustc_hash::{FxHashMap, FxHashSet}; +use serde_json::{json, Map, Value}; +use swc_core::common::{Span, Spanned as _}; + +use super::DefineValue; +use crate::{ + utils::eval::{evaluate_to_string, BasicEvaluatedExpression}, + visitors::JavascriptParser, + JavascriptParserPlugin, +}; + +static TYPEOF_OPERATOR_REGEXP: Lazy = + Lazy::new(|| Regex::new("^typeof\\s+").expect("should init `TYPEOF_OPERATOR_REGEXP`")); +static WEBPACK_REQUIRE_FUNCTION_REGEXP: Lazy = Lazy::new(|| { + Regex::new("__webpack_require__\\s*(!?\\.)") + .expect("should init `WEBPACK_REQUIRE_FUNCTION_REGEXP`") +}); +static WEBPACK_REQUIRE_IDENTIFIER_REGEXP: Lazy = Lazy::new(|| { + Regex::new("__webpack_require__").expect("should init `WEBPACK_REQUIRE_IDENTIFIER_REGEXP`") +}); + +type OnEvaluateIdentifier = dyn Fn( + &DefineRecord, + &mut JavascriptParser, + &str, /* Ident */ + u32, /* start */ + u32, /* end */ + ) -> Option + + Send + + Sync; + +type OnEvaluateTypeof = dyn Fn( + &DefineRecord, + &mut JavascriptParser, + u32, /* start */ + u32, /* end */ + ) -> Option + + Send + + Sync; + +type OnExpression = dyn Fn( + &DefineRecord, + &mut JavascriptParser, + Span, + u32, /* replace start */ + u32, /* replace end */ + &str, /* for name */ + ) -> Option + + Send + + Sync; + +type OnTypeof = dyn Fn(&DefineRecord, &mut JavascriptParser, u32 /* start */, u32 /* end */) -> Option + + Send + + Sync; + +struct DefineRecord { + code: Value, + on_evaluate_identifier: Option>, + on_evaluate_typeof: Option>, + on_expression: Option>, + on_typeof: Option>, +} + +impl std::fmt::Debug for DefineRecord { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DefineRecord") + .field("code", &self.code) + .finish_non_exhaustive() + } +} + +impl DefineRecord { + fn from_code(code: Value) -> DefineRecord { + Self { + code, + on_evaluate_identifier: None, + on_evaluate_typeof: None, + on_expression: None, + on_typeof: None, + } + } + + fn with_on_evaluate_identifier( + mut self, + on_evaluate_identifier: Box, + ) -> Self { + self.on_evaluate_identifier = Some(on_evaluate_identifier); + self + } + + fn with_on_evaluate_typeof(mut self, on_evaluate_typeof: Box) -> Self { + self.on_evaluate_typeof = Some(on_evaluate_typeof); + self + } + + fn with_on_expression(mut self, on_expression: Box) -> Self { + self.on_expression = Some(on_expression); + self + } + + fn with_on_typeof(mut self, on_typeof: Box) -> Self { + self.on_typeof = Some(on_typeof); + self + } +} + +#[derive(Default)] +struct ObjectDefineRecord { + object: Value, + on_evaluate_identifier: Option>, + on_expression: Option>, +} + +impl std::fmt::Debug for ObjectDefineRecord { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ObjectDefineRecord") + .field("object", &self.object) + .finish_non_exhaustive() + } +} + +type OnObjectEvaluateIdentifier = dyn Fn( + &ObjectDefineRecord, + &mut JavascriptParser, + &str, /* Ident */ + u32, /* start */ + u32, /* end */ + ) -> Option + + Send + + Sync; + +type OnObjectExpression = dyn Fn( + &ObjectDefineRecord, + &mut JavascriptParser, + Span, + u32, /* replace start */ + u32, /* replace end */ + &str, /* for name */ + ) -> Option + + Send + + Sync; + +impl ObjectDefineRecord { + fn from_code(obj: Value) -> Self { + assert!(matches!(obj, Value::Array(_) | Value::Object(_))); + Self { + object: obj, + on_evaluate_identifier: None, + on_expression: None, + } + } + + fn with_on_evaluate_identifier( + mut self, + on_evaluate_identifier: Box, + ) -> Self { + self.on_evaluate_identifier = Some(on_evaluate_identifier); + self + } + + fn with_on_expression(mut self, on_expression: Box) -> Self { + self.on_expression = Some(on_expression); + self + } +} + +#[derive(Debug, Default)] +pub(super) struct WalkData { + can_rename: FxHashSet>, + define_record: FxHashMap, DefineRecord>, + object_define_record: FxHashMap, ObjectDefineRecord>, +} + +pub(super) fn walk_definitions(definitions: &DefineValue) -> WalkData { + let mut data = WalkData::default(); + + fn apply_define_key(prefix: Cow, key: Cow, walk_data: &mut WalkData) { + let splitted: Vec<&str> = key.split('.').collect(); + if !splitted.is_empty() { + let iter = (0..splitted.len() - 1).map(|i| { + Arc::from( + core::iter::once(&&*prefix) + .chain(&splitted[0..i + 1]) + .join("."), + ) + }); + walk_data.can_rename.extend(iter) + } + } + + fn apply_define(key: Cow, code: &Value, walk_data: &mut WalkData) { + let is_typeof = TYPEOF_OPERATOR_REGEXP.is_match(&key); + let original_key = key; + let key = if is_typeof { + TYPEOF_OPERATOR_REGEXP.replace(&original_key, "") + } else { + original_key + }; + let key = Arc::::from(key); + let mut define_record = DefineRecord::from_code(code.clone()); + if !is_typeof { + walk_data.can_rename.insert(key.clone()); + define_record = define_record + .with_on_evaluate_identifier(Box::new(move |record, parser, _ident, start, end| { + let evaluated = parser + .evaluate(to_code(&record.code, None).into_owned(), "DefinePlugin") + .map(|mut evaluated| { + evaluated.set_range(start, end); + evaluated + }); + evaluated + })) + .with_on_expression(Box::new( + move |record, parser, span, start, end, for_name| { + let code = to_code(&record.code, Some(!parser.is_asi_position(span.lo))); + parser + .presentational_dependencies + .push(Box::new(dep(parser, code, for_name, start, end))); + Some(true) + }, + )); + } + + define_record = define_record + .with_on_evaluate_typeof(Box::new(move |record, parser, start, end| { + let code = to_code(&record.code, None); + let typeof_code = if is_typeof { + code + } else { + Cow::Owned(format!("typeof ({code})")) + }; + parser + .evaluate(typeof_code.into_owned(), "DefinePlugin") + .map(|mut evaluated| { + evaluated.set_range(start, end); + evaluated + }) + })) + .with_on_typeof(Box::new(move |record, parser, start, end| { + let code = to_code(&record.code, None); + let typeof_code = if is_typeof { + code + } else { + Cow::Owned(format!("typeof ({code})")) + }; + parser + .evaluate(typeof_code.to_string(), "DefinePlugin") + .and_then(|evaluated| { + if !evaluated.is_string() { + return None; + } + debug_assert!(!parser.in_short_hand); + parser.presentational_dependencies.push(Box::new(dep( + parser, + Cow::Owned(format!("{}", json!(evaluated.string()))), + "", + start, + end, + ))); + Some(true) + }) + })); + + walk_data.define_record.insert(key, define_record); + } + + fn object_evaluate_identifier(start: u32, end: u32) -> BasicEvaluatedExpression { + let mut evaluated = BasicEvaluatedExpression::new(); + evaluated.set_truthy(); + evaluated.set_side_effects(false); + evaluated.set_range(start, end); + evaluated + } + + fn apply_array_define(key: Cow, obj: &[Value], walk_data: &mut WalkData) { + let key = Arc::::from(key); + walk_data.can_rename.insert(key.clone()); + let define_record = ObjectDefineRecord::from_code(Value::Array(obj.to_owned())) + .with_on_evaluate_identifier(Box::new(move |_, _, _, start, end| { + Some(object_evaluate_identifier(start, end)) + })) + .with_on_expression(Box::new( + move |record, parser, span, start, end, for_name| { + let code = to_code(&record.object, Some(!parser.is_asi_position(span.lo))); + parser + .presentational_dependencies + .push(Box::new(dep(parser, code, for_name, start, end))); + Some(true) + }, + )); + walk_data.object_define_record.insert(key, define_record); + } + + fn apply_object_define(key: Cow, obj: &Map, walk_data: &mut WalkData) { + let key = Arc::::from(key); + walk_data.can_rename.insert(key.clone()); + let define_record = ObjectDefineRecord::from_code(Value::Object(obj.clone())) + .with_on_evaluate_identifier(Box::new(move |_, _, _, start, end| { + Some(object_evaluate_identifier(start, end)) + })) + .with_on_expression(Box::new( + move |record, parser, span, start, end, for_name| { + let code = to_code(&record.object, Some(!parser.is_asi_position(span.lo))); + parser + .presentational_dependencies + .push(Box::new(dep(parser, code, for_name, start, end))); + Some(true) + }, + )); + walk_data.object_define_record.insert(key, define_record); + } + + fn walk_code(code: &Value, prefix: Cow, key: Cow, walk_data: &mut WalkData) { + let prefix_for_object = || Cow::Owned(format!("{prefix}{key}.")); + if let Some(array) = code.as_array() { + walk_array(array, prefix_for_object(), walk_data); + apply_array_define(Cow::Owned(format!("{prefix}{key}")), array, walk_data); + } else if let Some(obj) = code.as_object() { + walk_object(obj, prefix_for_object(), walk_data); + apply_object_define(Cow::Owned(format!("{prefix}{key}")), obj, walk_data); + } else { + apply_define_key(prefix.clone(), Cow::Owned(key.to_string()), walk_data); + apply_define(Cow::Owned(format!("{prefix}{key}")), code, walk_data); + } + } + + fn walk_array(arr: &[Value], prefix: Cow, walk_data: &mut WalkData) { + arr.iter().enumerate().for_each(|(key, code)| { + walk_code(code, prefix.clone(), Cow::Owned(key.to_string()), walk_data) + }) + } + + fn walk_object(obj: &Map, prefix: Cow, walk_data: &mut WalkData) { + obj.iter().for_each(|(key, code)| { + walk_code(code, prefix.clone(), Cow::Owned(key.to_string()), walk_data) + }) + } + + let object = definitions.clone().into_iter().collect(); + walk_object(&object, "".into(), &mut data); + + data +} + +pub(super) struct DefineParserPlugin { + pub(super) walk_data: WalkData, +} + +impl JavascriptParserPlugin for DefineParserPlugin { + fn can_rename(&self, _: &mut JavascriptParser, str: &str) -> Option { + self.walk_data.can_rename.contains(str).then_some(true) + } + + fn evaluate_typeof( + &self, + parser: &mut JavascriptParser, + expr: &swc_core::ecma::ast::UnaryExpr, + for_name: &str, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(for_name) + && let Some(on_evaluate_typeof) = &record.on_evaluate_typeof + { + return on_evaluate_typeof(record, parser, expr.span.real_lo(), expr.span.hi.0); + } else if self.walk_data.object_define_record.get(for_name).is_some() { + return Some(evaluate_to_string( + "object".to_string(), + expr.span.real_lo(), + expr.span.hi.0, + )); + } + None + } + + fn evaluate_identifier( + &self, + parser: &mut JavascriptParser, + ident: &str, + start: u32, + end: u32, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(ident) + && let Some(on_evaluate_identifier) = &record.on_evaluate_identifier + { + return on_evaluate_identifier(record, parser, ident, start, end); + } else if let Some(record) = self.walk_data.object_define_record.get(ident) + && let Some(on_evaluate_identifier) = &record.on_evaluate_identifier + { + return on_evaluate_identifier(record, parser, ident, start, end); + } + None + } + + fn r#typeof( + &self, + parser: &mut JavascriptParser, + expr: &swc_core::ecma::ast::UnaryExpr, + for_name: &str, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(for_name) + && let Some(on_typeof) = &record.on_typeof + { + return on_typeof(record, parser, expr.span.real_lo(), expr.span.real_hi()); + } else if self.walk_data.object_define_record.get(for_name).is_some() { + debug_assert!(!parser.in_short_hand); + parser.presentational_dependencies.push(Box::new(dep( + parser, + Cow::Borrowed(r#""object""#), + for_name, + expr.span.real_lo(), + expr.span.real_hi(), + ))); + return Some(true); + } + None + } + + fn call( + &self, + parser: &mut JavascriptParser, + expr: &swc_core::ecma::ast::CallExpr, + for_name: &str, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + expr.span, + expr.callee.span().real_lo(), + expr.callee.span().real_hi(), + for_name, + ) + .map(|_| { + // FIXME: webpack use `walk_expression` here + parser.walk_expr_or_spread(&expr.args); + true + }); + } else if let Some(record) = self.walk_data.object_define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + expr.span, + expr.callee.span().real_lo(), + expr.callee.span().real_hi(), + for_name, + ) + .map(|_| { + // FIXME: webpack use `walk_expression` here + parser.walk_expr_or_spread(&expr.args); + true + }); + } + None + } + + fn member( + &self, + parser: &mut JavascriptParser, + expr: &swc_core::ecma::ast::MemberExpr, + for_name: &str, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + expr.span, + expr.span.real_lo(), + expr.span.real_hi(), + for_name, + ); + } else if let Some(record) = self.walk_data.object_define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + expr.span, + expr.span.real_lo(), + expr.span.real_hi(), + for_name, + ); + } + None + } + + fn identifier( + &self, + parser: &mut JavascriptParser, + ident: &swc_core::ecma::ast::Ident, + for_name: &str, + ) -> Option { + if let Some(record) = self.walk_data.define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + ident.span, + ident.span.real_lo(), + ident.span.real_hi(), + for_name, + ); + } else if let Some(record) = self.walk_data.object_define_record.get(for_name) + && let Some(on_expression) = &record.on_expression + { + return on_expression( + record, + parser, + ident.span, + ident.span.real_lo(), + ident.span.real_hi(), + for_name, + ); + } + None + } +} + +fn dep( + parser: &JavascriptParser, + code: Cow, + for_name: &str, + start: u32, + end: u32, +) -> ConstDependency { + let code = if parser.in_short_hand { + format!("{for_name}: {code}") + } else { + code.into_owned() + }; + + let to_const_dep = |requirements: Option| { + ConstDependency::new(start, end, code.clone().into_boxed_str(), requirements) + }; + + if WEBPACK_REQUIRE_FUNCTION_REGEXP.is_match(&code) { + to_const_dep(Some(RuntimeGlobals::REQUIRE)) + } else if WEBPACK_REQUIRE_IDENTIFIER_REGEXP.is_match(&code) { + to_const_dep(Some(RuntimeGlobals::REQUIRE_SCOPE)) + } else { + to_const_dep(None) + } +} + +fn to_code(code: &Value, asi_safe: Option) -> Cow { + fn wrap_ansi(code: Cow, is_arr: bool, asi_safe: Option) -> Cow { + match asi_safe { + Some(true) if is_arr => code, + Some(true) => Cow::Owned(format!("({code})")), + Some(false) if is_arr => Cow::Owned(format!(";{code}")), + Some(false) => Cow::Owned(format!(";({code})")), + None => code, + } + } + + match code { + Value::Null => Cow::Borrowed("null"), + Value::String(s) => Cow::Borrowed(s), + Value::Bool(b) => Cow::Borrowed(if *b { "true" } else { "false" }), + Value::Number(n) => Cow::Owned(n.to_string()), + Value::Array(arr) => { + let elements = arr.iter().map(|code| to_code(code, None)).join(","); + wrap_ansi(Cow::Owned(format!("[{elements}]")), true, asi_safe) + } + Value::Object(obj) => { + let elements = obj + .iter() + .map(|(key, value)| format!("{}:{}", json!(key), to_code(value, None))) + .join(","); + wrap_ansi(Cow::Owned(format!("{{ {elements} }}")), false, asi_safe) + } + } +} diff --git a/crates/rspack_plugin_javascript/src/utils/eval/eval_source.rs b/crates/rspack_plugin_javascript/src/utils/eval/eval_source.rs index da3998362c9..6effd9c60e2 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/eval_source.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/eval_source.rs @@ -1,7 +1,8 @@ -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; use rspack_core::EsVersion; use rspack_error::{miette::Severity, TraceableError}; +use serde_json::json; use swc_core::{ common::{FileName, Spanned}, ecma::parser::{parse_file_as_expr, EsSyntax, Syntax}, @@ -11,10 +12,10 @@ use super::BasicEvaluatedExpression; use crate::visitors::JavascriptParser; #[inline] -pub fn eval_source( +pub fn eval_source( parser: &mut JavascriptParser, source: String, - title: String, + error_title: T, ) -> Option { let cm: Arc = Default::default(); let fm = cm.new_source_file(FileName::Anon, source.clone()); @@ -34,8 +35,8 @@ pub fn eval_source( &fm, span.lo.0.saturating_sub(1) as usize, span.hi.0.saturating_sub(1) as usize, - format!("{title} warning"), - format!("failed to parse {:?}", source), + format!("{error_title} warning"), + format!("failed to parse {}", json!(source)), ) .with_severity(Severity::Warning), )); diff --git a/crates/rspack_plugin_javascript/src/utils/eval/eval_unary_expr.rs b/crates/rspack_plugin_javascript/src/utils/eval/eval_unary_expr.rs index 8382b6479f1..04d7e6ae3ac 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/eval_unary_expr.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/eval_unary_expr.rs @@ -1,6 +1,6 @@ use rspack_core::SpanExt; use swc_core::common::Spanned; -use swc_core::ecma::ast::{UnaryExpr, UnaryOp}; +use swc_core::ecma::ast::{Lit, UnaryExpr, UnaryOp}; use super::BasicEvaluatedExpression; use crate::parser_plugin::JavascriptParserPlugin; @@ -26,7 +26,22 @@ fn eval_typeof( // TODO: if let `MetaProperty`, `MemberExpression` ... let arg = parser.evaluate_expression(&expr.arg); if arg.is_unknown() { - None + let arg = expr.arg.unwrap_parens(); + if arg.as_fn_expr().is_some() || arg.as_class().is_some() { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("function".to_string()); + Some(res) + } else if let Some(unary) = arg.as_unary() + && matches!(unary.op, UnaryOp::Minus | UnaryOp::Plus) + && let Some(lit) = unary.arg.as_lit() + && matches!(lit, Lit::Num(_)) + { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("number".to_string()); + Some(res) + } else { + None + } } else if arg.is_string() { let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); res.set_string("string".to_string()); @@ -35,6 +50,22 @@ fn eval_typeof( let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); res.set_string("undefined".to_string()); Some(res) + } else if arg.is_number() { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("number".to_string()); + Some(res) + } else if arg.is_null() || arg.is_regexp() || arg.is_array() { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("object".to_string()); + Some(res) + } else if arg.is_bool() { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("boolean".to_string()); + Some(res) + } else if arg.is_bigint() { + let mut res = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi.0); + res.set_string("bigint".to_string()); + Some(res) } else { // TODO: `arg.is_wrapped()`... None 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 27e6fd44602..187e5bb8f4c 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -4,6 +4,7 @@ mod walk_block_pre; mod walk_pre; use std::borrow::Cow; +use std::fmt::Display; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; @@ -936,8 +937,12 @@ impl<'parser> JavascriptParser<'parser> { } } - pub fn evaluate(&mut self, source: String, title: String) -> Option { - eval::eval_source(self, source, title) + pub fn evaluate( + &mut self, + source: String, + error_title: T, + ) -> Option { + eval::eval_source(self, source, error_title) } // same as `JavascriptParser._initializeEvaluating` in webpack 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 f438e5b1abc..65f45177c74 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap @@ -227,32 +227,12 @@ Object { "minimize": false, "minimizer": Array [ SwcJsMinimizerRspackPlugin { - "_options": Object { - "compress": Object { - "passes": 1, - }, - "exclude": undefined, - "extractComments": undefined, - "format": Object { - "comments": false, - }, - "include": undefined, - "mangle": true, - "module": undefined, - "test": undefined, - }, + "_args": Array [], "affectedHooks": "compilation", "name": "SwcJsMinimizerRspackPlugin", }, LightningCssMinimizerRspackPlugin { - "_options": Object { - "browserslist": Array [ - "defaults", - ], - "errorRecovery": true, - "removeUnusedLocalIdents": true, - "unusedSymbols": Array [], - }, + "_args": Array [], "affectedHooks": undefined, "name": "LightningCssMinimizerRspackPlugin", }, diff --git a/packages/rspack-test-tools/tests/builtinCases/plugin-javascript/builtins-define/__snapshots__/output.snap.txt b/packages/rspack-test-tools/tests/builtinCases/plugin-javascript/builtins-define/__snapshots__/output.snap.txt index a8c0af9aa5a..3668bb0a739 100644 --- a/packages/rspack-test-tools/tests/builtinCases/plugin-javascript/builtins-define/__snapshots__/output.snap.txt +++ b/packages/rspack-test-tools/tests/builtinCases/plugin-javascript/builtins-define/__snapshots__/output.snap.txt @@ -11,30 +11,30 @@ __webpack_require__.r(__webpack_exports__); const lib = __webpack_require__("./lib.js"); const { DO_NOT_CONVERTED9 } = __webpack_require__("./lib.js"); -equal((true), true); +equal(true, true); // require("assert").deepStrictEqual(FALSE, false); -assert.deepStrictEqual((3 + 2), 5); -assert.deepStrictEqual((null), null); -assert.deepStrictEqual((undefined), undefined); +assert.deepStrictEqual(3 + 2, 5); +assert.deepStrictEqual(null, null); +assert.deepStrictEqual(undefined, undefined); // assert.equal(FUNCTION(5), 6); // assert.equal(typeof FUNCTION, "function"); -assert.deepStrictEqual((100.05), 100.05); -assert.deepStrictEqual((0), 0); +assert.deepStrictEqual(100.05, 100.05); +assert.deepStrictEqual(0, 0); let ZERO_OBJ = { ZERO: 0 }; assert.deepStrictEqual(ZERO_OBJ.ZERO, 0); -assert.deepStrictEqual(ZERO_OBJ[(0)], undefined); +assert.deepStrictEqual(ZERO_OBJ[0], undefined); assert.deepStrictEqual(ZERO_OBJ[0], undefined); assert.deepStrictEqual(ZERO_OBJ["ZERO"], 0); -assert.deepStrictEqual((BigInt(10000)), 10000n); -assert.deepStrictEqual((100000000000n), 100000000000n); -assert.deepStrictEqual((+0), 0); -assert.deepStrictEqual((-0), -0); -assert.deepStrictEqual((+100.25), 100.25); -assert.deepStrictEqual((-100.25), -100.25); -assert.deepStrictEqual(("string"), "string"); -assert.deepStrictEqual((""), ""); -assert.deepStrictEqual((/abc/i), /abc/i); -assert.deepStrictEqual((0).ABC, undefined); +assert.deepStrictEqual(BigInt(10000), 10000n); +assert.deepStrictEqual(100000000000n, 100000000000n); +assert.deepStrictEqual(+0, 0); +assert.deepStrictEqual(-0, -0); +assert.deepStrictEqual(+100.25, 100.25); +assert.deepStrictEqual(-100.25, -100.25); +assert.deepStrictEqual("string", "string"); +assert.deepStrictEqual("", ""); +assert.deepStrictEqual(/abc/i, /abc/i); +assert.deepStrictEqual(0.ABC, undefined); let error_count = 0; try { @@ -51,34 +51,34 @@ try { } catch (err) {} assert.deepStrictEqual(error_count, 2); -assert.deepStrictEqual(([300, ["six"]]), [300, ["six"]]); -assert.deepStrictEqual(([300, ["six"]])[0], 300); -assert.deepStrictEqual(([300, ["six"]])[0][1], undefined); -assert.deepStrictEqual(([300, ["six"]])[1], ["six"]); -assert.deepStrictEqual(([300, ["six"]])[1][0], "six"); -assert.deepStrictEqual(([300, ["six"]])[1][0][0], "s"); -assert.deepStrictEqual(([300, ["six"]])[(1)], ["six"]); -assert.deepStrictEqual(([300, ["six"]])[([300, ["six"]])], undefined); +assert.deepStrictEqual([300, ["six"]], [300, ["six"]]); +assert.deepStrictEqual([300, ["six"]][0], 300); +assert.deepStrictEqual([300, ["six"]][0][1], undefined); +assert.deepStrictEqual([300, ["six"]][1], ["six"]); +assert.deepStrictEqual([300, ["six"]][1][0], "six"); +assert.deepStrictEqual([300, ["six"]][1][0][0], "s"); +assert.deepStrictEqual([300, ["six"]][1], ["six"]); +assert.deepStrictEqual([300, ["six"]][[300, ["six"]]], undefined); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}), { +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}, { UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1 } }); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).OBJ, { NUM: 1 }); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).OBJ.NUM, 1); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).UNDEFINED, undefined); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).REGEXP, /def/i); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).STR, "string"); -assert.deepStrictEqual(({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}).AAA, undefined); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.OBJ, { NUM: 1 }); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.OBJ.NUM, 1); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.UNDEFINED, undefined); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.REGEXP, /def/i); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.STR, "string"); +assert.deepStrictEqual({UNDEFINED: undefined, REGEXP: /def/i, STR: "string", OBJ: { NUM: 1}}.AAA, undefined); -assert.deepStrictEqual((301), 301); -assert.deepStrictEqual(("302"), "302"); -assert.deepStrictEqual((303), 303); -assert.deepStrictEqual((304), 304); +assert.deepStrictEqual(301, 301); +assert.deepStrictEqual("302", "302"); +assert.deepStrictEqual(303, 303); +assert.deepStrictEqual(304, 304); -assert.deepStrictEqual((303).P4, undefined); // "303.P4" +assert.deepStrictEqual(303.P4, undefined); // "303.P4" try { error_count += 1; @@ -87,9 +87,9 @@ try { } catch (err) {} assert.deepStrictEqual(error_count, 3); -assert.deepStrictEqual(("302").P1, undefined); -assert.deepStrictEqual(("302").P3, undefined); -assert.deepStrictEqual(("302").P4, undefined); +assert.deepStrictEqual("302".P1, undefined); +assert.deepStrictEqual("302".P3, undefined); +assert.deepStrictEqual("302".P4, undefined); const DO_NOT_CONVERTED = 201; assert.deepStrictEqual(DO_NOT_CONVERTED, 201); @@ -115,7 +115,7 @@ const USELESS = { const DO_NOT_CONVERTED3 = 205; assert.deepStrictEqual(DO_NOT_CONVERTED3, 205); - const B = (0); + const B = 0; assert.deepStrictEqual(B, 0); let IN_BLOCK = 2; @@ -124,7 +124,7 @@ const USELESS = { { { { - assert.deepStrictEqual((205), 205); + assert.deepStrictEqual(205, 205); } } } @@ -132,14 +132,14 @@ const USELESS = { try { error_count += 1; - (SHOULD_BE_CONVERTED_IN_UNDEFINED_BLOCK); + SHOULD_BE_CONVERTED_IN_UNDEFINED_BLOCK; error_count += 1; } catch (err) {} assert.deepStrictEqual(error_count, 5); assert.deepStrictEqual(USELESS, { ZERO: 0 }); assert.deepStrictEqual({}.DO_NOT_CONVERTED5, undefined); -assert.deepStrictEqual(({}).DO_NOT_CONVERTED6, undefined); +assert.deepStrictEqual({}.DO_NOT_CONVERTED6, undefined); assert.deepStrictEqual(_lib__WEBPACK_IMPORTED_MODULE_0__.DO_NOT_CONVERTED7, 402); assert.deepStrictEqual(_lib__WEBPACK_IMPORTED_MODULE_0__["default"], 401); assert.deepStrictEqual(DO_NOT_CONVERTED9, 403); @@ -169,17 +169,17 @@ assert.deepStrictEqual(error_count, 6); // deepStrictEqual(error_count, 7); try { error_count += 1; - aa = (205); + aa = 205; error_count += 1; } catch (err) {} assert.deepStrictEqual(error_count, 7); -assert.deepStrictEqual((205) == 205, true); -assert.deepStrictEqual(207 == (205), false); +assert.deepStrictEqual(205 == 205, true); +assert.deepStrictEqual(207 == 205, false); try { error_count += 1; - (A1.A2.A3); + A1.A2.A3; error_count += 1; } catch (err) {} assert.deepStrictEqual(error_count, 8); diff --git a/packages/rspack-test-tools/tests/configCases/builtins/define/index.js b/packages/rspack-test-tools/tests/configCases/builtins/define/index.js index 7af36f84516..944a2db205f 100644 --- a/packages/rspack-test-tools/tests/configCases/builtins/define/index.js +++ b/packages/rspack-test-tools/tests/configCases/builtins/define/index.js @@ -65,9 +65,10 @@ it("should builtins define works", () => { expect(OBJECT.STR).toBe("string"); expect(OBJECT.AAA).toBe(undefined); - OBJECT2.FN(); - expect(typeof OBJECT2.FN).toBe("function"); - expect(OBJECT2).not.toBeUndefined(); + // Webpack does not support this + // OBJECT2.FN(); + // expect(typeof OBJECT2.FN).toBe("function"); + // expect(OBJECT2).not.toBeUndefined(); expect(P1.P2.P3).toBe(301); expect(P1.P2.P4).toBe("302"); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/index.js b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/index.js new file mode 100644 index 00000000000..fe3c9578b66 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/index.js @@ -0,0 +1,14 @@ +it("should define BIGINT", function() { + expect(BIGINT).toBe(9007199254740993n); + expect(typeof BIGINT).toBe("bigint"); + if (BIGINT !== 9007199254740993n) require("fail"); + if (typeof BIGINT !== "bigint") require("fail"); +}); +it("should define ZERO_BIGINT", function() { + expect(ZERO_BIGINT).toBe(0n); + expect(typeof ZERO_BIGINT).toBe("bigint"); + // TODO: support more evaluation types + // if (ZERO_BIGINT) require("fail"); + if (ZERO_BIGINT !== 0n) require("fail"); + if (typeof ZERO_BIGINT !== "bigint") require("fail"); +}); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/webpack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/webpack.config.js new file mode 100644 index 00000000000..2105b30016f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin-bigint/webpack.config.js @@ -0,0 +1,16 @@ +var DefinePlugin = require("@rspack/core").DefinePlugin; + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + output: { + environment: { + bigIntLiteral: true + } + }, + plugins: [ + new DefinePlugin({ + BIGINT: BigInt("9007199254740993"), + ZERO_BIGINT: BigInt(0) + }) + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/dir/a.js b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/dir/a.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/index.js b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/index.js new file mode 100644 index 00000000000..faf5c1e278e --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/index.js @@ -0,0 +1,267 @@ +function donotcallme() { + expect("asi unsafe call happened").toBe(false); +} + +it("should define FALSE", function() { + expect(FALSE).toBe(false); + expect(typeof FALSE).toBe("boolean"); + // var x = require(FALSE ? "fail" : "./dir/a"); + var y = FALSE ? require("fail") : require("./dir/a"); +}); +it("should define TRUE", function() { + expect(TRUE).toBe(true); + expect(typeof TRUE).toBe("boolean"); + // var x = require(TRUE ? "./dir/a" : "fail"); + var y = TRUE ? require("./dir/a") : require("fail"); +}); +it("should define CODE", function() { + expect(CODE).toBe(3); + expect(typeof CODE).toBe("number"); + if (CODE !== 3) require("fail"); + if (typeof CODE !== "number") require("fail"); +}); +it("should define FUNCTION", function() { + expect(FUNCTION(5)).toBe(6); + expect(typeof FUNCTION).toBe("function"); + if (typeof FUNCTION !== "function") require("fail"); +}); +it("should define NULL", function() { + expect(NULL).toBeNull(); + if (NULL) require("fail"); + if (NULL !== null) require("fail"); + if (typeof NULL !== "object") require("fail"); +}); +it("should define UNDEFINED", function() { + expect(typeof UNDEFINED).toBe("undefined"); + if (typeof UNDEFINED !== "undefined") require("fail"); +}); +it("should define NUMBER", function() { + expect(NUMBER).toBe(100.05); + expect(typeof NUMBER).toBe("number"); + if (NUMBER !== 100.05) require("fail"); + if (typeof NUMBER !== "number") require("fail"); +}); +it("should define ZERO", function() { + expect(ZERO).toBe(0); + expect(typeof ZERO).toBe("number"); + if (ZERO !== 0) require("fail"); + if (typeof ZERO !== "number") require("fail"); +}); +it("should define ONE", function() { + expect(ONE).toBe(1); + expect(typeof ONE).toBe("number"); + expect(42 / ONE).toBe(42); + if (ONE !== 1) require("fail"); + if (typeof ONE !== "number") require("fail"); +}); +it("should define BIGINT", function() { + expect(BIGINT).toBe(9007199254740993n); + expect(typeof BIGINT).toBe("bigint"); +}); +it("should define ZERO_BIGINT", function() { + expect(ZERO_BIGINT).toBe(0n); + expect(typeof BIGINT).toBe("bigint"); +}); +it("should define POSITIVE_ZERO", function() { + expect(POSITIVE_ZERO).toBe(+0); + expect(POSITIVE_ZERO).toBe(0); + expect(typeof POSITIVE_ZERO).toBe("number"); + expect(Object.is(POSITIVE_ZERO, 0)).toBe(true); + expect(Object.is(POSITIVE_ZERO, +0)).toBe(true); + expect(Object.is(POSITIVE_ZERO, -0)).toBe(false); + if (POSITIVE_ZERO) require("fail"); + if (typeof POSITIVE_ZERO !== "number") require("fail"); + // TODO: support more evaluation types + // if (POSITIVE_ZERO !== +0) require("fail"); + // TODO: support more evaluation types + // if (POSITIVE_ZERO != +0) require("fail"); + if (POSITIVE_ZERO !== 0) require("fail"); + if (POSITIVE_ZERO != 0) require("fail"); +}); +it("should define NEGATIVE_ZER0", function() { + expect(NEGATIVE_ZER0).toBe(-0); + expect(typeof NEGATIVE_ZER0).toBe("number"); + expect(Object.is(NEGATIVE_ZER0, 0)).toBe(false); + expect(Object.is(NEGATIVE_ZER0, +0)).toBe(false); + expect(Object.is(NEGATIVE_ZER0, -0)).toBe(true); + // TODO: support more evaluation types + // if (NEGATIVE_ZER0) require("fail"); + // TODO: support more evaluation types + // if (typeof NEGATIVE_ZER0 !== "number") require("fail"); + // TODO: support more evaluation types + // if (NEGATIVE_ZER0 !== +0) require("fail"); + // TODO: support more evaluation types + // if (NEGATIVE_ZER0 != +0) require("fail"); + // TODO: support more evaluation types + // if (NEGATIVE_ZER0 !== 0) require("fail"); + // TODO: support more evaluation types + // if (NEGATIVE_ZER0 != 0) require("fail"); +}); +it("should define NEGATIVE_NUMBER", function() { + expect(NEGATIVE_NUMBER).toBe(-100.25); + expect(typeof NEGATIVE_NUMBER).toBe("number"); + expect(100.25 / NEGATIVE_NUMBER).toBe(-1); + // TODO: support more evaluation types + // if (!NEGATIVE_NUMBER) require("fail"); + if (typeof NEGATIVE_NUMBER !== "number") require("fail"); +}); +it("should define POSITIVE_NUMBER", function() { + expect(POSITIVE_NUMBER).toBe(+100.25); + expect(typeof POSITIVE_NUMBER).toBe("number"); + expect(POSITIVE_NUMBER / 100.25).toBe(1); + // TODO: support more evaluation types + // if (!POSITIVE_NUMBER) require("fail"); + if (typeof POSITIVE_NUMBER !== "number") require("fail"); +}); +it("should define STRING", function() { + expect(STRING).toBe("string"); + expect(typeof STRING).toBe("string"); + if (!STRING) require("fail"); + if (typeof STRING !== "string") require("fail"); + if (STRING === "") require("fail"); + if (STRING == "") require("fail"); +}); +it("should define EMPTY_STRING", function() { + expect(EMPTY_STRING).toBe(""); + expect(typeof EMPTY_STRING).toBe("string"); + if (EMPTY_STRING) require("fail"); + if (typeof EMPTY_STRING !== "string") require("fail"); + if (EMPTY_STRING !== "") require("fail"); + if (EMPTY_STRING != "") require("fail"); +}); +it("should define REGEXP", function() { + expect(REGEXP.toString()).toBe("/abc/i"); + expect(typeof REGEXP).toBe("object"); + if (typeof REGEXP !== "object") require("fail"); +}); +it("should define OBJECT", function() { + var o = OBJECT; + expect(o.SUB.FUNCTION(10)).toBe(11); +}); +it("should define OBJECT.SUB.CODE", function() { + (donotcallme) + OBJECT; + (donotcallme) + OBJECT.SUB; + expect(typeof OBJECT.SUB.CODE).toBe("number"); + expect(OBJECT.SUB.CODE).toBe(3); + if (OBJECT.SUB.CODE !== 3) require("fail"); + if (typeof OBJECT.SUB.CODE !== "number") require("fail"); + + (function(sub) { + // should not crash + expect(sub.CODE).toBe(3); + })(OBJECT.SUB); +}); +it("should define OBJECT.SUB.STRING", function() { + expect(typeof OBJECT.SUB.STRING).toBe("string"); + expect(OBJECT.SUB.STRING).toBe("string"); + if (OBJECT.SUB.STRING !== "string") require("fail"); + if (typeof OBJECT.SUB.STRING !== "string") require("fail"); + + (function(sub) { + // should not crash + expect(sub.STRING).toBe("string"); + })(OBJECT.SUB); +}); +it("should define ARRAY", function() { + (donotcallme) + ARRAY; + expect(Array.isArray(ARRAY)).toBeTruthy(); + expect(ARRAY).toHaveLength(2); +}); +it("should define ARRAY[0]", function() { + expect(ARRAY[0]).toBe(2); +}); +it("should define ARRAY[1][0]", function() { + expect(Array.isArray(ARRAY[1])).toBeTruthy(); + expect(ARRAY[1]).toHaveLength(1); + expect(ARRAY[1][0]).toBe("six"); +}); +// FIXME: +// it("should define process.env.DEFINED_NESTED_KEY", function() { +// expect(process.env.DEFINED_NESTED_KEY).toBe(5); +// expect(typeof process.env.DEFINED_NESTED_KEY).toBe("number"); +// if (process.env.DEFINED_NESTED_KEY !== 5) require("fail"); +// if (typeof process.env.DEFINED_NESTED_KEY !== "number") require("fail"); + +// var x = process.env.DEFINED_NESTED_KEY; +// expect(x).toBe(5); + +// var indirect = process.env; +// expect(indirect.DEFINED_NESTED_KEY).toBe(5); + +// (function(env) { +// expect(env.DEFINED_NESTED_KEY).toBe(5); +// expect(typeof env.DEFINED_NESTED_KEY).toBe("number"); +// if (env.DEFINED_NESTED_KEY !== 5) require("fail"); +// if (typeof env.DEFINED_NESTED_KEY !== "number") require("fail"); + +// var x = env.DEFINED_NESTED_KEY; +// expect(x).toBe(5); +// })(process.env); +// }); +it("should define process.env.DEFINED_NESTED_KEY_STRING", function() { + if (process.env.DEFINED_NESTED_KEY_STRING !== "string") require("fail"); +}); +it("should assign to process.env", function() { + process.env.TEST = "test"; + expect(process.env.TEST).toBe("test"); +}); +it("should not have brackets on start", function() { + function f() { + throw new Error("should not be called"); + } + f // <- no semicolon here + OBJECT; +}); + +// FIXME: +// it("should not explode on recursive typeof calls", function() { +// expect(typeof wurst).toEqual("undefined"); // <- is recursively defined in config +// }); + +// FIXME: +// it("should not explode on recursive statements", function() { +// expect(function() { +// wurst; // <- is recursively defined in config +// }).toThrowError("suppe is not defined"); +// }); + +// FIXME: +// it("should evaluate composed expressions (issue 5100)", function() { +// if (!module.hot && process.env.DEFINED_NESTED_KEY_STRING === "string") { +// // ok +// } else { +// require("fail"); +// } +// }); + +// FIXME: +// it("should follow renamings in var (issue 5215)", function() { +// var _process$env = process.env, +// TEST = _process$env.TEST, +// DEFINED_NESTED_KEY = _process$env.DEFINED_NESTED_KEY; +// expect(TEST).toBe("test"); +// expect(DEFINED_NESTED_KEY).toBe(5); +// }); + +// FIXME: +// it("should check that runtimeValue callback argument is a module", function() { +// expect(RUNTIMEVALUE_CALLBACK_ARGUMENT_IS_A_MODULE).toEqual(true); +// }); + +// FIXME: +// it("should expand properly", function() { +// const a = require("./dir/a"); +// var tmp = ""; +// expect(require("./dir/" + A_DOT_J + tmp + "s")).toBe(a); +// expect(require("./dir/" + tmp + A_DOT_J + "s")).toBe(a); +// expect(require("./dir/" + tmp + A_DOT_J + tmp + "s")).toBe(a); +// expect(require("./dir/" + tmp + A_DOT_J + (tmp + "s"))).toBe(a); +// expect(require("./dir/" + tmp + (A_DOT_J + tmp + "s"))).toBe(a); +// expect(require("./dir/" + tmp + (A_DOT_J + tmp) + "s")).toBe(a); +// expect(require("./dir/" + (tmp + A_DOT_J + tmp + "s"))).toBe(a); +// expect(require("./dir/" + (tmp + A_DOT_J + tmp) + "s")).toBe(a); +// expect(require("./dir/" + (tmp + A_DOT_J) + tmp + "s")).toBe(a); +// }); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/webpack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/webpack.config.js new file mode 100644 index 00000000000..7a2af17a280 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/define-plugin/webpack.config.js @@ -0,0 +1,53 @@ +var DefinePlugin = require("@rspack/core").DefinePlugin; +const Module = require("@rspack/core").Module; +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + plugins: [ + new DefinePlugin({ + TRUE: true, + FALSE: false, + NULL: null, + UNDEFINED: undefined, + NUMBER: 100.05, + ZERO: 0, + ONE: 1, + STRING: '"string"', + EMPTY_STRING: '""', + BIGINT: BigInt("9007199254740993"), + ZERO_BIGINT: BigInt(0), + POSITIVE_ZERO: +0, + NEGATIVE_ZER0: -0, + NEGATIVE_NUMBER: -100.25, + POSITIVE_NUMBER: +100.25, + FUNCTION: /* istanbul ignore next */ function (a) { + return a + 1; + }, + CODE: "(1+2)", + REGEXP: /abc/i, + OBJECT: { + SUB: { + UNDEFINED: undefined, + FUNCTION: /* istanbul ignore next */ function (a) { + return a + 1; + }, + CODE: "(1+2)", + REGEXP: /abc/i, + STRING: JSON.stringify("string") + } + }, + ARRAY: [2, [JSON.stringify("six")]], + "process.env.DEFINED_NESTED_KEY": 5, + "process.env.DEFINED_NESTED_KEY_STRING": '"string"', + "typeof wurst": "typeof suppe", + "typeof suppe": "typeof wurst", + wurst: "suppe", + suppe: "wurst", + // RUNTIMEVALUE_CALLBACK_ARGUMENT_IS_A_MODULE: DefinePlugin.runtimeValue( + // function ({ module }) { + // return module instanceof Module; + // } + // ), + A_DOT_J: '"a.j"' + }) + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/index.js b/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/index.js new file mode 100644 index 00000000000..2c5f892d482 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/index.js @@ -0,0 +1,4 @@ +it("should evaluate `typeof xxx` of `DefinePlugin`", () => { + typeof window === "undefined" ? true : require("fail") + expect(typeof window).toBe("undefined") +}) diff --git a/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/rspack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/rspack.config.js new file mode 100644 index 00000000000..a929f7ba62c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/rspack-issue-7084/rspack.config.js @@ -0,0 +1,11 @@ +const rspack = require("@rspack/core"); +/** + * @type {import("@rspack/core").Configuration} + */ +module.exports = { + plugins: [ + new rspack.DefinePlugin({ + "typeof window": JSON.stringify("undefined") + }) + ] +} diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/plugins/define-plugin/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/plugins/define-plugin/stats.err index 38f1f05a41c..ae9c2bbb4b9 100644 --- a/packages/rspack-test-tools/tests/diagnosticsCases/plugins/define-plugin/stats.err +++ b/packages/rspack-test-tools/tests/diagnosticsCases/plugins/define-plugin/stats.err @@ -1,2 +1,2 @@ WARNING in ⚠ DefinePlugin: - │ Conflicting values for 'process.env.NODE_ENV' ('"production"' !== '"development"') \ No newline at end of file + │ Conflicting values for 'process.env.NODE_ENV' ("/"production/"" !== "/"development/"") \ No newline at end of file diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 06542f9cdde..19d0df63c14 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -39,24 +39,16 @@ import type { JsStatsWarning } from '@rspack/binding'; import { libCacheFacade } from './lib/CacheFacade'; import * as liteTapable from '@rspack/lite-tapable'; import { Logger as Logger_2 } from './logging/Logger'; -import { RawBannerPluginOptions } from '@rspack/binding'; import { RawCopyPattern } from '@rspack/binding'; -import { RawCopyRspackPluginOptions } from '@rspack/binding'; import type { RawCssExtractPluginOption } from '@rspack/binding'; -import { RawDynamicEntryPluginOptions } from '@rspack/binding'; -import { RawEntryPluginOptions } from '@rspack/binding'; -import { RawExternalsPluginOptions } from '@rspack/binding'; import { RawFuncUseCtx } from '@rspack/binding'; -import { RawHtmlRspackPluginOptions } from '@rspack/binding'; import { RawIgnorePluginOptions } from '@rspack/binding'; import type { RawLibraryOptions } from '@rspack/binding'; import { RawLightningCssMinimizerRspackPluginOptions } from '@rspack/binding'; -import { RawLimitChunkCountPluginOptions } from '@rspack/binding'; import type { RawOptions } from '@rspack/binding'; import { RawProgressPluginOptions } from '@rspack/binding'; import { RawRuntimeChunkOptions } from '@rspack/binding'; import { RawSourceMapDevToolPluginOptions } from '@rspack/binding'; -import { RawSwcJsMinimizerRspackPluginOptions } from '@rspack/binding'; import { registerGlobalTrace } from '@rspack/binding'; import { RspackOptionsNormalized as RspackOptionsNormalized_2 } from '.'; import sources = require('../compiled/webpack-sources'); @@ -402,9 +394,30 @@ export const BannerPlugin: { test?: string | RegExp | (string | RegExp)[] | undefined; }): { name: BuiltinPluginName; - _options: RawBannerPluginOptions; + _args: [args: string | ((args_0: { + hash: string; + chunk: JsChunk; + filename: string; + }, ...args_1: unknown[]) => string) | { + banner: (string | ((args_0: { + hash: string; + chunk: JsChunk; + filename: string; + }, ...args_1: unknown[]) => string)) & (string | ((args_0: { + hash: string; + chunk: JsChunk; + filename: string; + }, ...args_1: unknown[]) => string) | undefined); + entryOnly?: boolean | undefined; + exclude?: string | RegExp | (string | RegExp)[] | undefined; + include?: string | RegExp | (string | RegExp)[] | undefined; + raw?: boolean | undefined; + footer?: boolean | undefined; + stage?: number | undefined; + test?: string | RegExp | (string | RegExp)[] | undefined; + }]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -874,6 +887,12 @@ class CodeGenerationResult { get(sourceType: string): string; } +// @public (undocumented) +type CodeValue = RecursiveArrayOrRecord; + +// @public (undocumented) +type CodeValuePrimitive = null | undefined | RegExp | Function | string | number | boolean | bigint | undefined; + // @public (undocumented) interface CommonJsConfig extends BaseModuleConfig { // (undocumented) @@ -1447,9 +1466,9 @@ type ContextModuleFactoryBeforeResolveResult = false | { export const CopyRspackPlugin: { new (copy: CopyRspackPluginOptions): { name: BuiltinPluginName; - _options: RawCopyRspackPluginOptions; + _args: [copy: CopyRspackPluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -1661,15 +1680,15 @@ const cssParserOptions: z.ZodObject<{ export const DefinePlugin: { new (define: DefinePluginOptions): { name: BuiltinPluginName; - _options: Record; + _args: [define: DefinePluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; // @public (undocumented) -export type DefinePluginOptions = Record; +export type DefinePluginOptions = Record; // @public (undocumented) export type Dependencies = z.infer; @@ -1770,9 +1789,9 @@ class DirectoryWatcher extends EventEmitter { export const DynamicEntryPlugin: { new (context: string, entry: EntryDynamicNormalized): { name: BuiltinPluginName; - _options: RawDynamicEntryPluginOptions; + _args: [context: string, entry: EntryDynamicNormalized]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -1790,9 +1809,9 @@ export const electron: Electron; const ElectronTargetPlugin: { new (context?: string | undefined): { name: BuiltinPluginName; - _options: string; + _args: [context?: string | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -1801,9 +1820,9 @@ const ElectronTargetPlugin: { const EnableChunkLoadingPlugin: { new (type: any): { name: BuiltinPluginName; - _options: any; + _args: [type: any]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -1843,9 +1862,9 @@ class EnableLibraryPlugin extends RspackBuiltinPlugin { const EnableWasmLoadingPlugin: { new (type: any): { name: BuiltinPluginName; - _options: any; + _args: [type: any]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -2602,9 +2621,9 @@ export type EntryOptions = { export const EntryPlugin: { new (context: string, entry: string, options?: string | EntryOptions | undefined): { name: BuiltinPluginName; - _options: RawEntryPluginOptions; + _args: [context: string, entry: string, options?: string | EntryOptions | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -2849,9 +2868,9 @@ interface Es6Config extends BaseModuleConfig { export const EvalDevToolModulePlugin: { new (options: EvalDevToolModulePluginOptions): { name: BuiltinPluginName; - _options: EvalDevToolModulePluginOptions; + _args: [options: EvalDevToolModulePluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -2862,9 +2881,9 @@ export { EvalDevToolModulePluginOptions } export const EvalSourceMapDevToolPlugin: { new (options: SourceMapDevToolPluginOptions): { name: BuiltinPluginName; - _options: RawSourceMapDevToolPluginOptions; + _args: [options: SourceMapDevToolPluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -3160,9 +3179,25 @@ export const ExternalsPlugin: { request?: string | undefined; }, ...args_1: unknown[]) => Promise>))[]): { name: BuiltinPluginName; - _options: RawExternalsPluginOptions; + _args: [type: string, externals: string | RegExp | Record> | ((args_0: { + context?: string | undefined; + dependencyType?: string | undefined; + request?: string | undefined; + }, args_1: (args_0: Error | undefined, args_1: string | boolean | string[] | Record | undefined, args_2: "promise" | "module" | "commonjs" | "umd" | "amd" | "jsonp" | "import" | "commonjs2" | "var" | "assign" | "this" | "window" | "self" | "global" | "commonjs-module" | "commonjs-static" | "amd-require" | "umd2" | "system" | "script" | "node-commonjs" | undefined, ...args_3: unknown[]) => void, ...args_2: unknown[]) => unknown) | ((args_0: { + context?: string | undefined; + dependencyType?: string | undefined; + request?: string | undefined; + }, ...args_1: unknown[]) => Promise>) | (string | RegExp | Record> | ((args_0: { + context?: string | undefined; + dependencyType?: string | undefined; + request?: string | undefined; + }, args_1: (args_0: Error | undefined, args_1: string | boolean | string[] | Record | undefined, args_2: "promise" | "module" | "commonjs" | "umd" | "amd" | "jsonp" | "import" | "commonjs2" | "var" | "assign" | "this" | "window" | "self" | "global" | "commonjs-module" | "commonjs-static" | "amd-require" | "umd2" | "system" | "script" | "node-commonjs" | undefined, ...args_3: unknown[]) => void, ...args_2: unknown[]) => unknown) | ((args_0: { + context?: string | undefined; + dependencyType?: string | undefined; + request?: string | undefined; + }, ...args_1: unknown[]) => Promise>))[]]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -3231,9 +3266,9 @@ const falsy: z.ZodUnion<[z.ZodLiteral, z.ZodLiteral<0>, z.ZodLiteral<"">, const FetchCompileAsyncWasmPlugin: { new (): { name: BuiltinPluginName; - _options: void; + _args: []; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -3890,9 +3925,24 @@ export const HtmlRspackPlugin: { meta?: Record> | undefined; } | undefined): { name: BuiltinPluginName; - _options: RawHtmlRspackPluginOptions; + _args: [c?: { + filename?: string | undefined; + template?: string | undefined; + templateContent?: string | undefined; + templateParameters?: Record | undefined; + inject?: boolean | "head" | "body" | undefined; + publicPath?: string | undefined; + scriptLoading?: "module" | "blocking" | "defer" | undefined; + chunks?: string[] | undefined; + excludedChunks?: string[] | undefined; + sri?: "sha256" | "sha384" | "sha512" | undefined; + minify?: boolean | undefined; + title?: string | undefined; + favicon?: string | undefined; + meta?: Record> | undefined; + } | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -3972,9 +4022,9 @@ interface IDirent { export const IgnorePlugin: { new (options: IgnorePluginOptions): { name: BuiltinPluginName; - _options: RawIgnorePluginOptions; + _args: [options: IgnorePluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -4635,9 +4685,9 @@ const libraryType: z.ZodUnion<[z.ZodEnum<["var", "module", "assign", "assign-pro export const LightningCssMinimizerRspackPlugin: { new (options?: Partial | undefined): { name: BuiltinPluginName; - _options: RawLightningCssMinimizerRspackPluginOptions; + _args: [options?: Partial | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -4656,9 +4706,9 @@ type LimitChunkCountOptions = { const LimitChunkCountPlugin: { new (options: LimitChunkCountOptions): { name: BuiltinPluginName; - _options: RawLimitChunkCountPluginOptions; + _args: [options: LimitChunkCountOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -6055,9 +6105,9 @@ const nodeOptions: z.ZodObject<{ const NodeTargetPlugin: { new (): { name: BuiltinPluginName; - _options: undefined; + _args: []; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -8045,9 +8095,9 @@ const profile: z.ZodBoolean; export const ProgressPlugin: { new (progress?: ProgressPluginArgument): { name: BuiltinPluginName; - _options: RawProgressPluginOptions; + _args: [progress?: ProgressPluginArgument]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -8059,9 +8109,9 @@ export type ProgressPluginArgument = Partial | undefin export const ProvidePlugin: { new (provide: ProvidePluginOptions): { name: BuiltinPluginName; - _options: Record; + _args: [provide: ProvidePluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -8153,6 +8203,11 @@ type RawSourceMap = { // @public (undocumented) type ReactOptions = RawReactOptions | undefined; +// @public (undocumented) +type RecursiveArrayOrRecord = { + [index: string]: RecursiveArrayOrRecord; +} | Array> | T; + // @public (undocumented) export type Remotes = (RemotesItem | RemotesObject)[] | RemotesObject; @@ -12281,9 +12336,9 @@ const ruleSetUseItem: z.ZodUnion<[z.ZodString, z.ZodObject<{ const RuntimeChunkPlugin: { new (options: RawRuntimeChunkOptions): { name: BuiltinPluginName; - _options: RawRuntimeChunkOptions; + _args: [options: RawRuntimeChunkOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -12494,9 +12549,9 @@ interface SourceMap { export const SourceMapDevToolPlugin: { new (options: SourceMapDevToolPluginOptions): { name: BuiltinPluginName; - _options: RawSourceMapDevToolPluginOptions; + _args: [options: SourceMapDevToolPluginOptions]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -13088,9 +13143,9 @@ const strictModuleExceptionHandling: z.ZodBoolean; export const SwcCssMinimizerRspackPlugin: { new (options?: any): { name: BuiltinPluginName; - _options: undefined; + _args: [options?: any]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -13099,9 +13154,9 @@ export const SwcCssMinimizerRspackPlugin: { export const SwcJsMinimizerRspackPlugin: { new (options?: SwcJsMinimizerRspackPluginOptions | undefined): { name: BuiltinPluginName; - _options: RawSwcJsMinimizerRspackPluginOptions; + _args: [options?: SwcJsMinimizerRspackPluginOptions | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; @@ -13721,9 +13776,9 @@ export const webworker: Webworker; const WebWorkerTemplatePlugin: { new (): { name: BuiltinPluginName; - _options: undefined; + _args: []; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; - raw(): BuiltinPlugin; + raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; diff --git a/packages/rspack/src/builtin-plugin/DefinePlugin.ts b/packages/rspack/src/builtin-plugin/DefinePlugin.ts index 44821fbff18..97dec93197b 100644 --- a/packages/rspack/src/builtin-plugin/DefinePlugin.ts +++ b/packages/rspack/src/builtin-plugin/DefinePlugin.ts @@ -2,17 +2,69 @@ import { BuiltinPluginName } from "@rspack/binding"; import { create } from "./base"; -export type DefinePluginOptions = Record; +export type DefinePluginOptions = Record; export const DefinePlugin = create( BuiltinPluginName.DefinePlugin, - (define: DefinePluginOptions): Record => { - const entries = Object.entries(define).map(([key, value]) => { - if (typeof value !== "string") { - value = value === undefined ? "undefined" : JSON.stringify(value); - } - return [key, value]; - }); - return Object.fromEntries(entries); + function (define: DefinePluginOptions): NormalizedCodeValue { + let supportsBigIntLiteral = + this.options.output.environment?.bigIntLiteral ?? false; + return normalizeValue(define, supportsBigIntLiteral); }, "compilation" ); + +const normalizeValue = ( + define: DefinePluginOptions, + supportsBigIntLiteral: boolean +) => { + const normalizePrimitive = ( + p: CodeValuePrimitive + ): NormalizedCodeValuePrimitive => { + if (p === undefined) { + return "undefined"; + } else if (Object.is(p, -0)) { + return "-0"; + } else if (p instanceof RegExp) { + return p.toString(); + } else if (typeof p === "function") { + return "(" + p.toString() + ")"; + } else if (typeof p === "bigint") { + return supportsBigIntLiteral ? `${p}n` : `BigInt("${p}")`; + } else { + // assume `p` is a valid JSON value + return p; + } + }; + const normalizeObject = (define: CodeValue): NormalizedCodeValue => { + if (Array.isArray(define)) { + return define.map(normalizeObject); + } else if (define instanceof RegExp) { + return normalizePrimitive(define); + } else if (define && typeof define === "object") { + let keys = Object.keys(define); + return Object.fromEntries(keys.map(k => [k, normalizeObject(define[k])])); + } else { + return normalizePrimitive(define); + } + }; + return normalizeObject(define); +}; + +type CodeValue = RecursiveArrayOrRecord; +type CodeValuePrimitive = + | null + | undefined + | RegExp + | Function + | string + | number + | boolean + | bigint + | undefined; +type NormalizedCodeValuePrimitive = null | string | number | boolean; +type NormalizedCodeValue = RecursiveArrayOrRecord; + +type RecursiveArrayOrRecord = + | { [index: string]: RecursiveArrayOrRecord } + | Array> + | T; diff --git a/packages/rspack/src/builtin-plugin/base.ts b/packages/rspack/src/builtin-plugin/base.ts index 0f557025ef8..74efa5cd042 100644 --- a/packages/rspack/src/builtin-plugin/base.ts +++ b/packages/rspack/src/builtin-plugin/base.ts @@ -49,23 +49,23 @@ export function createBuiltinPlugin( export function create( name: binding.BuiltinPluginName, - resolve: (...args: T) => R, + resolve: (this: Compiler, ...args: T) => R, // `affectedHooks` is used to inform `createChildCompile` about which builtin plugin can be reserved. // However, this has a drawback as it doesn't represent the actual condition but merely serves as an indicator. affectedHooks?: AffectedHooks ) { class Plugin extends RspackBuiltinPlugin { name = name; - _options: R; + _args: T; affectedHooks = affectedHooks; constructor(...args: T) { super(); - this._options = resolve(...args); + this._args = args; } - raw(): binding.BuiltinPlugin { - return createBuiltinPlugin(name, this._options); + raw(compiler: Compiler): binding.BuiltinPlugin { + return createBuiltinPlugin(name, resolve.apply(compiler, this._args)); } } diff --git a/website/docs/en/plugins/webpack/define-plugin.mdx b/website/docs/en/plugins/webpack/define-plugin.mdx index 241e21da267..b7a86c391c0 100644 --- a/website/docs/en/plugins/webpack/define-plugin.mdx +++ b/website/docs/en/plugins/webpack/define-plugin.mdx @@ -17,7 +17,27 @@ new rspack.DefinePlugin({ ## Options -- **Type:** `Record` +- **Type:** + +```ts +type CodeValue = RecursiveArrayOrRecord; +type CodeValuePrimitive = + | null + | undefined + | RegExp + | Function + | string + | number + | boolean + | bigint + | undefined; +type RecursiveArrayOrRecord = + | { [index: string]: RecursiveArrayOrRecord } + | Array> + | T; + +type DefinePluginOptions = Record; +``` ## Examples diff --git a/website/docs/zh/plugins/webpack/define-plugin.mdx b/website/docs/zh/plugins/webpack/define-plugin.mdx index 6c1b34aa7d8..1b02bea0613 100644 --- a/website/docs/zh/plugins/webpack/define-plugin.mdx +++ b/website/docs/zh/plugins/webpack/define-plugin.mdx @@ -17,7 +17,27 @@ new rspack.DefinePlugin({ ## 选项 -- **类型:** `Record` +- **类型:** + +```ts +type CodeValue = RecursiveArrayOrRecord; +type CodeValuePrimitive = + | null + | undefined + | RegExp + | Function + | string + | number + | boolean + | bigint + | undefined; +type RecursiveArrayOrRecord = + | { [index: string]: RecursiveArrayOrRecord } + | Array> + | T; + +type DefinePluginOptions = Record; +``` ## 示例