From 92a9fd75187a78d966dee983351867d511e2d0c4 Mon Sep 17 00:00:00 2001 From: ahabhgk Date: Mon, 22 Apr 2024 21:23:03 +0800 Subject: [PATCH] refactor: context module evaluating --- .../context/import_context_dependency.rs | 34 +- .../src/parser_plugin/import_parser_plugin.rs | 312 +++++++----------- .../parser_plugin/initialize_evaluating.rs | 85 +++++ .../src/plugin/mod.rs | 2 +- .../rspack_plugin_javascript/src/runtime.rs | 6 +- .../src/utils/eval/eval_tpl_expr.rs | 47 ++- .../src/utils/eval/mod.rs | 43 ++- .../rspack_plugin_javascript/src/utils/mod.rs | 5 - .../dependency/context_dependency_helper.rs | 95 ++++-- .../src/visitors/dependency/parser/mod.rs | 3 +- crates/rspack_util/src/diff_mode.rs | 8 + crates/rspack_util/src/lib.rs | 1 + .../webpack-module-placeholder-plugin.ts | 5 +- .../module-context-module/src/index.js | 19 ++ .../module-context-module/src/sub/a.js | 3 + .../module-context-module/src/sub/a1.js | 3 + .../module-context-module/src/sub/abc.js | 3 + 17 files changed, 397 insertions(+), 277 deletions(-) create mode 100644 crates/rspack_util/src/diff_mode.rs create mode 100644 packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a.js create mode 100644 packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a1.js create mode 100644 packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/abc.js diff --git a/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs index 97c162cce886..338a6e953b87 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs @@ -1,6 +1,6 @@ -use rspack_core::{module_raw, parse_resource, AsModuleDependency, ContextDependency}; -use rspack_core::{normalize_context, DependencyCategory, DependencyId, DependencyTemplate}; +use rspack_core::{module_raw, AsModuleDependency, ContextDependency}; use rspack_core::{ContextOptions, Dependency, TemplateReplaceSource}; +use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; use rspack_core::{DependencyType, ErrorSpan, TemplateContext}; use super::create_resource_identifier_for_context_dependency; @@ -113,34 +113,10 @@ impl DependencyTemplate for ImportContextDependency { return; } - source.replace(self.callee_start, self.callee_end, &expr, None); - - if !self.replaces.is_empty() { - for (content, start, end) in &self.replaces { - source.replace(*start, *end, content, None); - } - } else { - let context = normalize_context(&self.options.context); - let query = parse_resource(&self.options.request).and_then(|data| data.query); - if !context.is_empty() || query.is_some() { - source.insert(self.callee_end, "(", None); - if !context.is_empty() { - source.insert( - self.args_end, - format!(".replace('{context}', './')").as_str(), - None, - ); - } - if let Some(query) = query { - source.insert( - self.args_end, - format!(".replace('{query}', '')").as_str(), - None, - ); - } - source.insert(self.args_end, ")", None); - } + for (content, start, end) in &self.replaces { + source.replace(*start, *end - 1, content, None); } + source.replace(self.callee_start, self.callee_end, &expr, None); } fn dependency_id(&self) -> Option { diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs index 6af8ec16ec23..3b6492d404f5 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs @@ -5,12 +5,11 @@ use rspack_core::{ use rspack_core::{ChunkGroupOptions, ContextMode}; use rspack_core::{ContextNameSpaceObject, ContextOptions, DependencyCategory, SpanExt}; use swc_core::common::Spanned; -use swc_core::ecma::ast::{CallExpr, Callee, Expr, Lit}; -use swc_core::ecma::atoms::Atom; +use swc_core::ecma::ast::{CallExpr, Callee}; use super::JavascriptParserPlugin; use crate::dependency::{ImportContextDependency, ImportDependency, ImportEagerDependency}; -use crate::visitors::{parse_order_string, scanner_context_module, ContextModuleScanResult}; +use crate::visitors::{create_context_dependency, parse_order_string, ContextModuleScanResult}; use crate::webpack_comment::try_extract_webpack_magic_comment; pub struct ImportParserPlugin; @@ -45,203 +44,134 @@ impl JavascriptParserPlugin for ImportParserPlugin { .map(|o| o.dynamic_import_prefetch) .and_then(|o| o.get_order()); - match dyn_imported.expr.as_ref() { - Expr::Lit(Lit::Str(imported)) => { - let magic_comment_options = try_extract_webpack_magic_comment( - parser.source_file, - &parser.comments, - node.span, - imported.span, - &mut parser.warning_diagnostics, - ); - if magic_comment_options - .get_webpack_ignore() - .unwrap_or_default() - { - return None; - } - let mode = magic_comment_options - .get_webpack_mode() - .map(|x| DynamicImportMode::from(x.as_str())); - let chunk_name = magic_comment_options - .get_webpack_chunk_name() - .map(|x| x.to_owned()); - let chunk_prefetch = magic_comment_options - .get_webpack_prefetch() - .and_then(|x| parse_order_string(x.as_str())); - let chunk_preload = magic_comment_options - .get_webpack_preload() - .and_then(|x| parse_order_string(x.as_str())); - let span = ErrorSpan::from(node.span); - if matches!( - mode.unwrap_or(dynamic_import_mode), - DynamicImportMode::Eager - ) { - let dep = ImportEagerDependency::new( - node.span.real_lo(), - node.span.real_hi(), - imported.value.clone(), - Some(span), - // TODO scan dynamic import referenced exports - None, - ); - parser.dependencies.push(Box::new(dep)); - return Some(true); - } - let dep = Box::new(ImportDependency::new( + let magic_comment_options = try_extract_webpack_magic_comment( + parser.source_file, + &parser.comments, + node.span, + dyn_imported.expr.span(), + &mut parser.warning_diagnostics, + ); + if magic_comment_options + .get_webpack_ignore() + .unwrap_or_default() + { + return None; + } + + let mode = magic_comment_options + .get_webpack_mode() + .map(|x| DynamicImportMode::from(x.as_str())); + let chunk_name = magic_comment_options + .get_webpack_chunk_name() + .map(|x| x.to_owned()); + let chunk_prefetch = magic_comment_options + .get_webpack_prefetch() + .and_then(|x| parse_order_string(x.as_str())); + let chunk_preload = magic_comment_options + .get_webpack_preload() + .and_then(|x| parse_order_string(x.as_str())); + + let param = parser.evaluate_expression(dyn_imported.expr.as_ref()); + + if param.is_string() { + let span = ErrorSpan::from(node.span); + if matches!( + mode.unwrap_or(dynamic_import_mode), + DynamicImportMode::Eager + ) { + let dep = ImportEagerDependency::new( node.span.real_lo(), node.span.real_hi(), - imported.value.clone(), + param.string().as_str().into(), Some(span), // TODO scan dynamic import referenced exports None, - )); - let mut block = AsyncDependenciesBlock::new( - *parser.module_identifier, - Some(DependencyLocation::new(span.start, span.end)), - None, - vec![dep], ); - block.set_group_options(GroupOptions::ChunkGroup(ChunkGroupOptions::new( - chunk_name, - chunk_preload.or(dynamic_import_preload), - chunk_prefetch.or(dynamic_import_prefetch), - ))); - parser.blocks.push(block); - Some(true) + parser.dependencies.push(Box::new(dep)); + return Some(true); } - Expr::Tpl(tpl) if tpl.quasis.len() == 1 => { - let magic_comment_options = try_extract_webpack_magic_comment( - parser.source_file, - &parser.comments, - node.span, - tpl.span, - &mut parser.warning_diagnostics, - ); - let mode = magic_comment_options - .get_webpack_mode() - .map(|x| DynamicImportMode::from(x.as_str())); - let chunk_name = magic_comment_options - .get_webpack_chunk_name() - .map(|x| x.to_owned()); - let chunk_prefetch = magic_comment_options - .get_webpack_prefetch() - .and_then(|x| parse_order_string(x.as_str())); - let chunk_preload = magic_comment_options - .get_webpack_preload() - .and_then(|x| parse_order_string(x.as_str())); - let request = Atom::from( - tpl - .quasis - .first() - .expect("should have one quasis") - .raw - .to_string(), - ); - let span = ErrorSpan::from(node.span); - if matches!( - mode.unwrap_or(dynamic_import_mode), - DynamicImportMode::Eager - ) { - let dep = ImportEagerDependency::new( - node.span.real_lo(), - node.span.real_hi(), - request, - Some(span), - // TODO scan dynamic import referenced exports - None, - ); - parser.dependencies.push(Box::new(dep)); - return Some(true); - } - let dep = Box::new(ImportDependency::new( - node.span.real_lo(), + let dep = Box::new(ImportDependency::new( + node.span.real_lo(), + node.span.real_hi(), + param.string().as_str().into(), + Some(span), + // TODO scan dynamic import referenced exports + None, + )); + let mut block = AsyncDependenciesBlock::new( + *parser.module_identifier, + Some(DependencyLocation::new(span.start, span.end)), + None, + vec![dep], + ); + block.set_group_options(GroupOptions::ChunkGroup(ChunkGroupOptions::new( + chunk_name, + chunk_preload.or(dynamic_import_preload), + chunk_prefetch.or(dynamic_import_prefetch), + ))); + parser.blocks.push(block); + Some(true) + } else { + let ContextModuleScanResult { + context, + reg, + query, + fragment, + replaces, + } = create_context_dependency(¶m); + let magic_comment_options = try_extract_webpack_magic_comment( + parser.source_file, + &parser.comments, + node.span, + dyn_imported.span(), + &mut parser.warning_diagnostics, + ); + let _mode = magic_comment_options + .get_webpack_mode() + .map(|x| DynamicImportMode::from(x.as_str())); + let chunk_name = magic_comment_options + .get_webpack_chunk_name() + .map(|x| x.to_owned()); + let chunk_prefetch = magic_comment_options + .get_webpack_prefetch() + .and_then(|x| parse_order_string(x.as_str())); + let chunk_preload = magic_comment_options + .get_webpack_preload() + .and_then(|x| parse_order_string(x.as_str())); + parser + .dependencies + .push(Box::new(ImportContextDependency::new( + import_call.span.real_lo(), + import_call.span.real_hi(), node.span.real_hi(), - request, - Some(span), - None, - )); - let mut block = AsyncDependenciesBlock::new( - *parser.module_identifier, - Some(DependencyLocation::new(span.start, span.end)), - None, - vec![dep], - ); - block.set_group_options(GroupOptions::ChunkGroup(ChunkGroupOptions::new( - chunk_name, - chunk_preload.or(dynamic_import_preload), - chunk_prefetch.or(dynamic_import_prefetch), - ))); - parser.blocks.push(block); - // FIXME: align `parser.walk_expression` to webpack, which put into `context_dependency_helper` - parser.walk_template_expression(tpl); - Some(true) - } - _ => { - let Some(ContextModuleScanResult { - context, - reg, - query, - fragment, - replaces, - }) = scanner_context_module(dyn_imported.expr.as_ref()) - else { - return None; - }; - let magic_comment_options = try_extract_webpack_magic_comment( - parser.source_file, - &parser.comments, - node.span, - dyn_imported.span(), - &mut parser.warning_diagnostics, - ); - let _mode = magic_comment_options - .get_webpack_mode() - .map(|x| DynamicImportMode::from(x.as_str())); - let chunk_name = magic_comment_options - .get_webpack_chunk_name() - .map(|x| x.to_owned()); - let chunk_prefetch = magic_comment_options - .get_webpack_prefetch() - .and_then(|x| parse_order_string(x.as_str())); - let chunk_preload = magic_comment_options - .get_webpack_preload() - .and_then(|x| parse_order_string(x.as_str())); - parser - .dependencies - .push(Box::new(ImportContextDependency::new( - import_call.span.real_lo(), - import_call.span.real_hi(), - node.span.real_hi(), - ContextOptions { - mode: ContextMode::Lazy, - recursive: true, - reg_exp: context_reg_exp(®, ""), - include: None, - exclude: None, - category: DependencyCategory::Esm, - request: format!("{}{}{}", context.clone(), query, fragment), - context, - namespace_object: if parser.build_meta.strict_harmony_module { - ContextNameSpaceObject::Strict - } else { - ContextNameSpaceObject::Bool(true) - }, - group_options: Some(GroupOptions::ChunkGroup(ChunkGroupOptions::new( - chunk_name, - chunk_preload.or(dynamic_import_preload), - chunk_prefetch.or(dynamic_import_prefetch), - ))), - start: node.span().real_lo(), - end: node.span().real_hi(), + ContextOptions { + mode: ContextMode::Lazy, + recursive: true, + reg_exp: context_reg_exp(®, ""), + include: None, + exclude: None, + category: DependencyCategory::Esm, + request: format!("{}{}{}", context.clone(), query, fragment), + context, + namespace_object: if parser.build_meta.strict_harmony_module { + ContextNameSpaceObject::Strict + } else { + ContextNameSpaceObject::Bool(true) }, - replaces, - Some(node.span.into()), - ))); - // FIXME: align `parser.walk_expression` to webpack, which put into `context_dependency_helper` - parser.walk_expression(&dyn_imported.expr); - Some(true) - } + group_options: Some(GroupOptions::ChunkGroup(ChunkGroupOptions::new( + chunk_name, + chunk_preload.or(dynamic_import_preload), + chunk_prefetch.or(dynamic_import_prefetch), + ))), + start: node.span().real_lo(), + end: node.span().real_hi(), + }, + replaces, + Some(node.span.into()), + ))); + // FIXME: align `parser.walk_expression` to webpack, which put into `context_dependency_helper` + parser.walk_expression(&dyn_imported.expr); + Some(true) } } } diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/initialize_evaluating.rs b/crates/rspack_plugin_javascript/src/parser_plugin/initialize_evaluating.rs index 508772d1999d..d9d35598f41d 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/initialize_evaluating.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/initialize_evaluating.rs @@ -5,6 +5,7 @@ use crate::utils::eval::BasicEvaluatedExpression; const SLICE_METHOD_NAME: &str = "slice"; const REPLACE_METHOD_NAME: &str = "replace"; +const CONCAT_METHOD_NAME: &str = "concat"; pub struct InitializeEvaluating; @@ -66,6 +67,90 @@ impl JavascriptParserPlugin for InitializeEvaluating { res.set_string(s); res.set_side_effects(param.could_have_side_effects()); return Some(res); + } else if property == CONCAT_METHOD_NAME && (param.is_string() || param.is_wrapped()) { + let mut string_suffix: Option = None; + let mut has_unknown_params = false; + let mut inner_exprs = Vec::new(); + for arg in expr.args.iter().rev() { + if arg.spread.is_some() { + return None; + } + let arg_expr = parser.evaluate_expression(&arg.expr); + if has_unknown_params || (!arg_expr.is_string() && !arg_expr.is_number()) { + has_unknown_params = true; + inner_exprs.push(arg_expr); + continue; + } + let mut new_string = if arg_expr.is_string() { + arg_expr.string().to_owned() + } else { + format!("{}", arg_expr.number()) + }; + if let Some(string_suffix) = &string_suffix { + new_string += string_suffix.string(); + } + let mut eval = BasicEvaluatedExpression::with_range( + arg_expr.range().0, + string_suffix.as_ref().unwrap_or(&arg_expr).range().1, + ); + eval.set_string(new_string); + eval.set_side_effects( + string_suffix + .as_ref() + .map(|s| s.could_have_side_effects()) + .unwrap_or_else(|| arg_expr.could_have_side_effects()), + ); + string_suffix = Some(eval); + } + if has_unknown_params { + let prefix = if param.is_string() { + Some(param) + } else { + param.prefix() + } + .cloned(); + inner_exprs.reverse(); + let inner = if param.is_wrapped() + && let Some(wrapped_inner_expressions) = param.wrapped_inner_expressions() + { + let mut wrapped_inner_exprs = wrapped_inner_expressions.to_vec(); + wrapped_inner_exprs.extend(inner_exprs); + wrapped_inner_exprs + } else { + inner_exprs + }; + let mut eval = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi().0); + eval.set_wrapped(prefix, string_suffix, inner); + return Some(eval); + } else if param.is_wrapped() { + let postfix = string_suffix.or_else(|| param.postfix().cloned()); + let inner = if param.is_wrapped() + && let Some(wrapped_inner_expressions) = param.wrapped_inner_expressions() + { + let mut wrapped_inner_exprs = wrapped_inner_expressions.to_vec(); + wrapped_inner_exprs.extend(inner_exprs); + wrapped_inner_exprs + } else { + inner_exprs + }; + let mut eval = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi().0); + eval.set_wrapped(param.prefix().cloned(), postfix, inner); + return Some(eval); + } else { + let mut new_string = param.string().to_owned(); + if let Some(string_suffix) = &string_suffix { + new_string += string_suffix.string(); + } + let mut eval = BasicEvaluatedExpression::with_range(expr.span.real_lo(), expr.span.hi().0); + eval.set_string(new_string); + eval.set_side_effects( + string_suffix + .as_ref() + .map(|s| s.could_have_side_effects()) + .unwrap_or_else(|| param.could_have_side_effects()), + ); + return Some(eval); + } } None diff --git a/crates/rspack_plugin_javascript/src/plugin/mod.rs b/crates/rspack_plugin_javascript/src/plugin/mod.rs index 2770191b7ed0..7269362ab28c 100644 --- a/crates/rspack_plugin_javascript/src/plugin/mod.rs +++ b/crates/rspack_plugin_javascript/src/plugin/mod.rs @@ -24,11 +24,11 @@ use rspack_core::{ use rspack_error::Result; use rspack_hash::RspackHash; use rspack_hook::plugin; +use rspack_util::diff_mode::is_diff_mode; use rspack_util::fx_hash::{BuildFxHasher, FxDashMap}; pub use side_effects_flag_plugin::*; use crate::runtime::{render_chunk_modules, render_iife, render_runtime_modules, stringify_array}; -use crate::utils::is_diff_mode; static COMPILATION_DRIVES_MAP: Lazy> = Lazy::new(Default::default); diff --git a/crates/rspack_plugin_javascript/src/runtime.rs b/crates/rspack_plugin_javascript/src/runtime.rs index f48471143bfd..21574a184c47 100644 --- a/crates/rspack_plugin_javascript/src/runtime.rs +++ b/crates/rspack_plugin_javascript/src/runtime.rs @@ -5,9 +5,9 @@ use rspack_core::{ SourceType, }; use rspack_error::{error, Result}; +use rspack_util::diff_mode::is_diff_mode; use rustc_hash::FxHashSet as HashSet; -use crate::utils::is_diff_mode; use crate::{JsPlugin, RenderJsModuleContentArgs}; pub fn render_chunk_modules( @@ -141,7 +141,7 @@ fn render_module( if is_diff_mode() { sources.add(RawSource::from(format!( "\n{}\n", - to_normal_comment(&format!("start::{}::{}", module_id, module.identifier())) + to_normal_comment(&format!("start::{}", module.identifier())) ))); } sources.add(RawSource::from(format!( @@ -158,7 +158,7 @@ fn render_module( if is_diff_mode() { sources.add(RawSource::from(format!( "\n{}\n", - to_normal_comment(&format!("end::{}::{}", module_id, module.identifier())) + to_normal_comment(&format!("end::{}", module.identifier())) ))); } sources.add(RawSource::from(",\n")); diff --git a/crates/rspack_plugin_javascript/src/utils/eval/eval_tpl_expr.rs b/crates/rspack_plugin_javascript/src/utils/eval/eval_tpl_expr.rs index 14db091e4b30..e9c92097f2f2 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/eval_tpl_expr.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/eval_tpl_expr.rs @@ -1,6 +1,6 @@ use rspack_core::SpanExt; use swc_core::common::Spanned; -use swc_core::ecma::ast::Tpl; +use swc_core::ecma::ast::{TaggedTpl, Tpl}; use super::BasicEvaluatedExpression; use crate::visitors::JavascriptParser; @@ -14,18 +14,20 @@ pub enum TemplateStringKind { fn get_simplified_template_result( scanner: &mut JavascriptParser, + kind: TemplateStringKind, node: &Tpl, ) -> (Vec, Vec) { let mut quasis: Vec = vec![]; let mut parts: Vec = vec![]; for i in 0..node.quasis.len() { let quasi_expr = &node.quasis[i]; - // FIXME: `quasi_exp.cooked` -> `quasi_exp[kind]` - // and the kind is a argument - let quasi = quasi_expr - .cooked - .as_ref() - .expect("quasic should be not empty"); + let quasi = match kind { + TemplateStringKind::Cooked => quasi_expr + .cooked + .as_ref() + .expect("quasic should be not empty"), + TemplateStringKind::Raw => &quasi_expr.raw, + }; if i > 0 { let len = parts.len(); let prev_expr = &mut parts[len - 1]; @@ -33,9 +35,16 @@ fn get_simplified_template_result( if !expr.could_have_side_effects() && let Some(str) = expr.as_string() { + // merge quasi + expr + quasi when expr is a const string prev_expr.set_string(format!("{}{}{}", prev_expr.string(), str, quasi)); - prev_expr.set_range(prev_expr.range().0, prev_expr.range().1); + prev_expr.set_range(prev_expr.range().0, quasi_expr.span_hi().0); // prev_expr.set_expression(None); + + // also merge for quasis + let prev_expr = &mut quasis[len - 1]; + prev_expr.set_string(format!("{}{}{}", prev_expr.string(), str, quasi)); + prev_expr.set_range(prev_expr.range().0, quasi_expr.span_hi().0); + continue; } parts.push(expr); @@ -59,14 +68,32 @@ pub fn eval_tpl_expression( scanner: &mut JavascriptParser, tpl: &Tpl, ) -> Option { - let (quasis, mut parts) = get_simplified_template_result(scanner, tpl); + let kind = TemplateStringKind::Cooked; + let (quasis, mut parts) = get_simplified_template_result(scanner, kind, tpl); if parts.len() == 1 { let mut part = parts.remove(0); part.set_range(tpl.span().real_lo(), tpl.span().hi().0); Some(part) } else { let mut res = BasicEvaluatedExpression::with_range(tpl.span().real_lo(), tpl.span().hi().0); - res.set_template_string(quasis, parts, TemplateStringKind::Cooked); + res.set_template_string(quasis, parts, kind); Some(res) } } + +pub fn eval_tagged_tpl_expression( + scanner: &mut JavascriptParser, + tagged_tpl: &TaggedTpl, +) -> Option { + let tag = scanner.evaluate_expression(&tagged_tpl.tag); + if !tag.is_identifier() || tag.identifier() != "String.raw" { + return None; + }; + let kind = TemplateStringKind::Raw; + let tpl = &tagged_tpl.tpl; + let (quasis, parts) = get_simplified_template_result(scanner, kind, tpl); + let mut res = + BasicEvaluatedExpression::with_range(tagged_tpl.span().real_lo(), tagged_tpl.span().hi().0); + res.set_template_string(quasis, parts, kind); + Some(res) +} diff --git a/crates/rspack_plugin_javascript/src/utils/eval/mod.rs b/crates/rspack_plugin_javascript/src/utils/eval/mod.rs index 424fdec9ee21..80b95ad91bb6 100644 --- a/crates/rspack_plugin_javascript/src/utils/eval/mod.rs +++ b/crates/rspack_plugin_javascript/src/utils/eval/mod.rs @@ -16,7 +16,9 @@ pub use self::eval_call_expr::eval_call_expression; pub use self::eval_cond_expr::eval_cond_expression; pub use self::eval_lit_expr::{eval_lit_expr, eval_prop_name}; pub use self::eval_new_expr::eval_new_expression; -pub use self::eval_tpl_expr::{eval_tpl_expression, TemplateStringKind}; +pub use self::eval_tpl_expr::{ + eval_tagged_tpl_expression, eval_tpl_expression, TemplateStringKind, +}; pub use self::eval_unary_expr::eval_unary_expression; use crate::visitors::ExportedVariableInfo; @@ -48,7 +50,7 @@ type Regexp = (String, String); // (expr, flags) // I really don't want there has many alloc, maybe this can be optimized after // parse finished. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BasicEvaluatedExpression { ty: Ty, range: Option, @@ -208,8 +210,32 @@ impl BasicEvaluatedExpression { Some(self.bool().to_string()) } else if self.is_null() { Some("null".to_string()) + } else if self.is_undefined() { + Some("undefined".to_string()) } else if self.is_string() { Some(self.string().to_string()) + } else if self.is_number() { + Some(self.number().to_string()) + } else if self.is_array() { + let mut arr = Vec::new(); + for item in self.items() { + if let Some(item) = item.as_string() { + arr.push(item) + } else { + return None; + } + } + Some(format!("[{}]", arr.join(", "))) + } else if self.is_template_string() { + let mut s = String::new(); + for p in self.parts() { + if let Some(p) = p.as_string() { + s += &p; + } else { + return None; + } + } + Some(s) } else { None } @@ -440,6 +466,14 @@ impl BasicEvaluatedExpression { self.postfix.as_deref() } + pub fn wrapped_inner_expressions(&self) -> Option<&[BasicEvaluatedExpression]> { + assert!( + self.is_wrapped(), + "wrapped_inner_expressions is only used in wrapped" + ); + self.wrapped_inner_expressions.as_deref() + } + pub fn template_string_kind(&self) -> TemplateStringKind { assert!(self.is_template_string()); self @@ -463,6 +497,11 @@ impl BasicEvaluatedExpression { .expect("quasis must exists for template string") } + pub fn items(&self) -> &Vec { + assert!(self.is_array()); + self.items.as_ref().expect("items must exists for array") + } + pub fn number(&self) -> Number { assert!(self.is_number()); self.number.expect("number must exists in ty::number") diff --git a/crates/rspack_plugin_javascript/src/utils/mod.rs b/crates/rspack_plugin_javascript/src/utils/mod.rs index c38ba1f49083..94ef49c3b67d 100644 --- a/crates/rspack_plugin_javascript/src/utils/mod.rs +++ b/crates/rspack_plugin_javascript/src/utils/mod.rs @@ -68,8 +68,3 @@ pub fn ecma_parse_error_deduped_to_rspack_error( ) .with_kind(diagnostic_kind) } - -pub fn is_diff_mode() -> bool { - let is_diff_mode = std::env::var("RSPACK_DIFF").ok().unwrap_or_default(); - is_diff_mode == "true" -} diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/context_dependency_helper.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/context_dependency_helper.rs index 9f4fa144070c..0dfad4e1fbc2 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/context_dependency_helper.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/context_dependency_helper.rs @@ -2,19 +2,16 @@ use std::borrow::Cow; use itertools::Itertools; use rspack_core::parse_resource; -use swc_core::ecma::ast::Expr; +use rspack_util::json_stringify; use super::context_helper::{quote_meta, split_context_from_prefix}; use super::ContextModuleScanResult; -use crate::utils::eval::BasicEvaluatedExpression; +use crate::utils::eval::{BasicEvaluatedExpression, TemplateStringKind}; // FIXME: delete this after `parserOptions.wrappedContextRegExp.source` const DEFAULT_WRAPPED_CONTEXT_REGEXP: &str = ".*"; -pub fn create_context_dependency( - param: &BasicEvaluatedExpression, - _expr: &Expr, -) -> Option { +pub fn create_context_dependency(param: &BasicEvaluatedExpression) -> ContextModuleScanResult { if param.is_template_string() { let quasis = param.quasis(); let Some(prefix) = quasis.first() else { @@ -44,18 +41,47 @@ pub fn create_context_dependency( DEFAULT_WRAPPED_CONTEXT_REGEXP, quasis[1..quasis.len() - 1] .iter() - .map(|q| quote_meta(q.string().as_str())) + .map(|q| quote_meta(q.string().as_str()) + DEFAULT_WRAPPED_CONTEXT_REGEXP) .join(""), quote_meta(&postfix) ); - Some(ContextModuleScanResult { + + let mut replaces = Vec::new(); + let parts = param.parts(); + for (i, part) in parts.iter().enumerate() { + if i % 2 == 0 { + if i == 0 { + let value = format!( + "{}{prefix}", + match param.template_string_kind() { + TemplateStringKind::Cooked => "`", + TemplateStringKind::Raw => "String.raw`", + } + ); + replaces.push((value, param.range().0, part.range().1)); + } else if i == parts.len() - 1 { + let value = format!("{postfix}`"); + replaces.push((value, part.range().0, param.range().1)); + } else { + let value = match param.template_string_kind() { + TemplateStringKind::Cooked => { + json_stringify(part.string()).trim_matches('"').to_owned() + } + TemplateStringKind::Raw => part.string().to_owned(), + }; + let range = part.range(); + replaces.push((value, range.0, range.1)); + } + } + } + + ContextModuleScanResult { context, reg, query, fragment, - replaces: Vec::new(), - }) - // TODO: `replaces` in context module + replaces, + } // TODO: `critical` in context module } else if param.is_wrapped() && let prefix_is_string = param @@ -68,25 +94,17 @@ pub fn create_context_dependency( .unwrap_or_default() && (prefix_is_string || postfix_is_string) { - let prefix_raw = if prefix_is_string { - Cow::Borrowed( - param - .prefix() - .map(|prefix| prefix.string()) - .expect("must exist"), - ) + let (prefix_raw, prefix_range) = if prefix_is_string { + let prefix = param.prefix().expect("must exist"); + (Cow::Borrowed(prefix.string()), Some(prefix.range())) } else { - Cow::Owned(String::new()) + (Cow::Owned(String::new()), None) }; - let postfix_raw = if postfix_is_string { - Cow::Borrowed( - param - .postfix() - .map(|prefix| prefix.string()) - .expect("must exist"), - ) + let (postfix_raw, postfix_range) = if postfix_is_string { + let postfix = param.postfix().expect("must exist"); + (Cow::Borrowed(postfix.string()), Some(postfix.range())) } else { - Cow::Owned(String::new()) + (Cow::Owned(String::new()), None) }; let (context, prefix) = split_context_from_prefix(prefix_raw.to_string()); @@ -105,18 +123,31 @@ pub fn create_context_dependency( quote_meta(&postfix) ); - Some(ContextModuleScanResult { + let mut replaces = Vec::new(); + if let Some(prefix_range) = prefix_range { + replaces.push((json_stringify(&prefix), prefix_range.0, prefix_range.1)) + } + if let Some(postfix_range) = postfix_range { + replaces.push((json_stringify(&postfix), postfix_range.0, postfix_range.1)) + } + + ContextModuleScanResult { context, reg, query, fragment, - replaces: Vec::new(), - }) + replaces, + } - // TODO: `replaces` in context module // TODO: `critical` in context module // TODO: handle `param.wrappedInnerExpressions` } else { - None + ContextModuleScanResult { + context: String::from("."), + reg: String::new(), + query: String::new(), + fragment: String::new(), + replaces: Vec::new(), + } } } 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 415909030ce5..c28af109ef57 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -73,7 +73,7 @@ pub struct ExpressionExpressionInfo { pub root_info: ExportedVariableInfo, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ExportedVariableInfo { Name(String), VariableInfo(VariableInfoId), @@ -696,6 +696,7 @@ impl JavascriptParser<'_> { fn evaluating(&mut self, expr: &Expr) -> Option { match expr { Expr::Tpl(tpl) => eval::eval_tpl_expression(self, tpl), + Expr::TaggedTpl(tagged_tpl) => eval::eval_tagged_tpl_expression(self, tagged_tpl), Expr::Lit(lit) => eval::eval_lit_expr(lit), Expr::Cond(cond) => eval::eval_cond_expression(self, cond), Expr::Unary(unary) => eval::eval_unary_expression(self, unary), diff --git a/crates/rspack_util/src/diff_mode.rs b/crates/rspack_util/src/diff_mode.rs new file mode 100644 index 000000000000..7915f6ec106d --- /dev/null +++ b/crates/rspack_util/src/diff_mode.rs @@ -0,0 +1,8 @@ +use once_cell::sync::Lazy; + +static IS_DIFF_MODE: Lazy = + Lazy::new(|| std::env::var("RSPACK_DIFF").ok().unwrap_or_default()); + +pub fn is_diff_mode() -> bool { + *IS_DIFF_MODE == "true" +} diff --git a/crates/rspack_util/src/lib.rs b/crates/rspack_util/src/lib.rs index 7e52690dbc7c..0bcd5f0700ae 100644 --- a/crates/rspack_util/src/lib.rs +++ b/crates/rspack_util/src/lib.rs @@ -3,6 +3,7 @@ mod merge; pub mod comparators; +pub mod diff_mode; pub mod ext; pub mod fx_hash; pub mod identifier; diff --git a/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts b/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts index 62db142108f6..0d957fce4db5 100644 --- a/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts +++ b/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts @@ -106,12 +106,11 @@ export class WebpackModulePlaceholderPlugin { let footer = cacheEntry.footer; if (header === undefined) { const identifier = module.identifier(); - const moduleId = compilation.chunkGraph.getModuleId(module); header = new RawSource( - `\n${Template.toNormalComment(`start::${moduleId}::${identifier}`)}\n` + `\n${Template.toNormalComment(`start::${identifier}`)}\n` ); footer = new RawSource( - `\n${Template.toNormalComment(`end::${moduleId}::${identifier}`)}\n` + `\n${Template.toNormalComment(`end::${identifier}`)}\n` ); cacheEntry.header = header; cacheEntry.footer = footer; diff --git a/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/index.js b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/index.js index 28f80efc5243..f94d21c55258 100644 --- a/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/index.js +++ b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/index.js @@ -1,3 +1,22 @@ const m = "module.js"; import("./" + m); import("./empty/" + m); +import(`./${m}`); + +import(`./sub/${"a"}`); +import(`./sub/${"a"}${1}`); +import("./sub/" + "a"); +import("./sub/" + "a" + 1); +import("./sub/".concat("a")); +import("./sub/".concat("a", 1)); + +const testFileName = "a"; +import(`./sub/${testFileName}`); +import(`./sub/${testFileName}bc`); +import("./sub/" + testFileName); +import("./sub/" + testFileName + ""); +import("./sub/" + testFileName + "bc"); +import("./sub/".concat(testFileName)); +import("./sub/".concat(testFileName).concat("")); +import("./sub/".concat(testFileName).concat("bc")); +// import("./sub/".concat(testFileName).concat("?queryString")) diff --git a/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a.js b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a.js new file mode 100644 index 000000000000..4c580819a087 --- /dev/null +++ b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a.js @@ -0,0 +1,3 @@ +export default function a() { + return "a" + __resourceQuery; +} diff --git a/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a1.js b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a1.js new file mode 100644 index 000000000000..52175d0d8684 --- /dev/null +++ b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/a1.js @@ -0,0 +1,3 @@ +export default function a1() { + return "a1" + __resourceQuery; +} diff --git a/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/abc.js b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/abc.js new file mode 100644 index 000000000000..fcb067b3c7d9 --- /dev/null +++ b/packages/rspack-test-tools/tests/runtimeDiffCases/module-context-module/src/sub/abc.js @@ -0,0 +1,3 @@ +export default function abc() { + return "abc" + __resourceQuery; +}