diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 70db5dab060..006c3eaaf21 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -45,6 +45,7 @@ export class JsCompilation { addMissingDependencies(deps: Array): void addBuildDependencies(deps: Array): void rebuildModule(moduleIdentifiers: Array, f: (...args: any[]) => any): void + importModule(request: string, publicPath: string | undefined | null, baseUri: string | undefined | null, originalModule: string | undefined | null, originalModuleContext: string | undefined | null, callback: (...args: any[]) => any): void } export class JsStats { @@ -253,6 +254,11 @@ export interface JsAssetInfoRelated { sourceMap?: string } +export interface JsBuildTimeExecutionOption { + publicPath?: string + baseUri?: string +} + export interface JsChunk { __inner_ukey: number __inner_groups: Array @@ -302,8 +308,20 @@ export interface JsCompatSource { export interface JsExecuteModuleArg { entry: string + request: string + options: JsBuildTimeExecutionOption runtimeModules: Array codegenResults: JsCodegenerationResults + id: number +} + +export interface JsExecuteModuleResult { + fileDependencies: Array + contextDependencies: Array + buildDependencies: Array + missingDependencies: Array + assets: Array + id: number } export interface JsHooks { @@ -392,6 +410,7 @@ export interface JsLoaderContext { * @internal */ diagnosticsExternal: ExternalObject<'Diagnostic[]'> + _moduleIdentifier: string } export interface JsModule { diff --git a/crates/node_binding/src/plugins/mod.rs b/crates/node_binding/src/plugins/mod.rs index e1a872bd11c..3ddaa730ddc 100644 --- a/crates/node_binding/src/plugins/mod.rs +++ b/crates/node_binding/src/plugins/mod.rs @@ -10,10 +10,11 @@ use rspack_binding_values::{ ToJsCompatSource, }; use rspack_binding_values::{BeforeResolveData, JsAssetEmittedArgs, ToJsModule}; -use rspack_binding_values::{CreateModuleData, JsExecuteModuleArg}; +use rspack_binding_values::{CreateModuleData, JsBuildTimeExecutionOption, JsExecuteModuleArg}; use rspack_binding_values::{JsResolveForSchemeInput, JsResolveForSchemeResult}; use rspack_core::{ - Chunk, ChunkAssetArgs, Compilation, ModuleIdentifier, NormalModuleAfterResolveArgs, RuntimeModule, + BuildTimeExecutionOption, Chunk, ChunkAssetArgs, Compilation, ModuleIdentifier, + NormalModuleAfterResolveArgs, RuntimeModule, }; use rspack_core::{NormalModuleBeforeResolveArgs, PluginNormalModuleFactoryAfterResolveOutput}; use rspack_core::{ @@ -71,7 +72,7 @@ pub struct JsHooksAdapter { ThreadsafeFunction, pub succeed_module_tsfn: ThreadsafeFunction, pub still_valid_module_tsfn: ThreadsafeFunction, - pub execute_module_tsfn: ThreadsafeFunction>, + pub execute_module_tsfn: ThreadsafeFunction, pub runtime_module_tsfn: ThreadsafeFunction>, } @@ -832,11 +833,14 @@ impl rspack_core::Plugin for JsHooksAdapter { fn execute_module( &self, entry: ModuleIdentifier, + request: &str, + options: &BuildTimeExecutionOption, runtime_modules: Vec, codegen_results: &rspack_core::CodeGenerationResults, - ) -> rspack_error::Result> { + id: u32, + ) -> rspack_error::Result<()> { if self.is_hook_disabled(&Hook::ExecuteModule) { - return Ok(None); + return Ok(()); } self @@ -844,11 +848,17 @@ impl rspack_core::Plugin for JsHooksAdapter { .call( JsExecuteModuleArg { entry: entry.to_string(), + request: request.into(), + options: JsBuildTimeExecutionOption { + public_path: options.public_path.clone(), + base_uri: options.base_uri.clone(), + }, runtime_modules: runtime_modules .into_iter() .map(|id| id.to_string()) .collect(), codegen_results: codegen_results.clone().into(), + id, }, ThreadsafeFunctionCallMode::NonBlocking, ) @@ -1031,7 +1041,7 @@ impl JsHooksAdapter { js_fn_into_threadsafe_fn!(succeed_module, env); let still_valid_module_tsfn: ThreadsafeFunction = js_fn_into_threadsafe_fn!(still_valid_module, env); - let execute_module_tsfn: ThreadsafeFunction> = + let execute_module_tsfn: ThreadsafeFunction = js_fn_into_threadsafe_fn!(execute_module, env); let runtime_module_tsfn: ThreadsafeFunction> = js_fn_into_threadsafe_fn!(runtime_module, env); diff --git a/crates/rspack_binding_options/src/options/raw_module/js_loader.rs b/crates/rspack_binding_options/src/options/raw_module/js_loader.rs index 96a41c3e620..92854b169f4 100644 --- a/crates/rspack_binding_options/src/options/raw_module/js_loader.rs +++ b/crates/rspack_binding_options/src/options/raw_module/js_loader.rs @@ -215,6 +215,7 @@ fn sync_loader_context( } else { loader_context.additional_data.remove::(); } + loader_context.asset_filenames = loader_result.asset_filenames.into_iter().collect(); Ok(()) } @@ -256,6 +257,9 @@ pub struct JsLoaderContext { /// @internal #[napi(ts_type = "ExternalObject<'Diagnostic[]'>")] pub diagnostics_external: External>, + + #[napi(js_name = "_moduleIdentifier")] + pub module_identifier: String, } impl TryFrom<&mut rspack_core::LoaderContext<'_, rspack_core::LoaderRunnerContext>> @@ -316,6 +320,7 @@ impl TryFrom<&mut rspack_core::LoaderContext<'_, rspack_core::LoaderRunnerContex additional_data_external: External::new(cx.additional_data.clone()), context_external: External::new(cx.context.clone()), diagnostics_external: External::new(cx.__diagnostics.drain(..).collect()), + module_identifier: cx.context.module.to_string(), }) } } @@ -414,6 +419,7 @@ pub struct JsLoaderResult { pub context_dependencies: Vec, pub missing_dependencies: Vec, pub build_dependencies: Vec, + pub asset_filenames: Vec, pub source_map: Option, pub additional_data: Option, pub additional_data_external: External, @@ -439,6 +445,12 @@ impl napi::bindgen_prelude::FromNapiValue for JsLoaderResult { ) -> napi::bindgen_prelude::Result { let obj = napi::bindgen_prelude::Object::from_napi_value(env, napi_val)?; let content_: Option = obj.get("content")?; + let asset_filenames_: Vec = obj.get("assetFilenames")?.ok_or_else(|| { + napi::bindgen_prelude::Error::new( + napi::bindgen_prelude::Status::InvalidArg, + format!("Missing field `{}`", "assetFilenames"), + ) + })?; let file_dependencies_: Vec = obj.get("fileDependencies")?.ok_or_else(|| { napi::bindgen_prelude::Error::new( napi::bindgen_prelude::Status::InvalidArg, @@ -493,6 +505,7 @@ impl napi::bindgen_prelude::FromNapiValue for JsLoaderResult { context_dependencies: context_dependencies_, missing_dependencies: missing_dependencies_, build_dependencies: build_dependencies_, + asset_filenames: asset_filenames_, source_map: source_map_, additional_data: additional_data_, additional_data_external: additional_data_external_, diff --git a/crates/rspack_binding_values/src/codegen_result.rs b/crates/rspack_binding_values/src/codegen_result.rs index a430d8cd16a..b5618c69828 100644 --- a/crates/rspack_binding_values/src/codegen_result.rs +++ b/crates/rspack_binding_values/src/codegen_result.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use napi_derive::napi; -use rspack_core::{CodeGenerationResult, CodeGenerationResults}; +use rspack_core::{get_runtime_key, CodeGenerationResult, CodeGenerationResults}; #[napi(object)] #[derive(Debug)] @@ -36,23 +36,27 @@ impl From for JsCodegenerationResults { .map .into_iter() .map(|(module_id, runtime_result_map)| { - ( - module_id.to_string(), - runtime_result_map - .map - .into_iter() - .map(|(k, result_id)| { - ( - k, - id_result_map - .get(&result_id) - .expect("should exist codegenResult") - .clone() - .into(), - ) - }) - .collect(), - ) + let mut runtime_map: HashMap = Default::default(); + match &runtime_result_map.mode { + rspack_core::RuntimeMode::Empty => {} + rspack_core::RuntimeMode::SingleEntry => { + runtime_map.insert( + get_runtime_key(runtime_result_map.single_runtime.expect("exist")), + id_result_map + .get(&runtime_result_map.single_value.expect("TODO")) + .expect("TODO") + .clone() + .into(), + ); + } + rspack_core::RuntimeMode::Map => { + runtime_result_map.map.into_iter().for_each(|(k, v)| { + runtime_map.insert(k, id_result_map.get(&v).expect("TODO").clone().into()); + }); + } + }; + + (module_id.to_string(), runtime_map) }) .collect(), } diff --git a/crates/rspack_binding_values/src/compilation.rs b/crates/rspack_binding_values/src/compilation.rs index feb6a1d5d5f..ffb785af4d6 100644 --- a/crates/rspack_binding_values/src/compilation.rs +++ b/crates/rspack_binding_values/src/compilation.rs @@ -429,6 +429,67 @@ impl JsCompilation { ) }) } + + #[allow(clippy::too_many_arguments)] + #[napi] + pub fn import_module( + &'static self, + env: Env, + request: String, + public_path: Option, + base_uri: Option, + original_module: Option, + original_module_context: Option, + callback: JsFunction, + ) -> Result<()> { + callbackify(env, callback, async { + self + .inner + .import_module( + request, + public_path, + base_uri, + original_module.map(|s| s.into()), + original_module_context.map(|ctx| Box::new(rspack_core::Context::new(ctx))), + ) + .await + .map(|res| JsExecuteModuleResult { + file_dependencies: res + .file_dependencies + .into_iter() + .map(|d| d.to_string_lossy().to_string()) + .collect(), + context_dependencies: res + .context_dependencies + .into_iter() + .map(|d| d.to_string_lossy().to_string()) + .collect(), + build_dependencies: res + .build_dependencies + .into_iter() + .map(|d| d.to_string_lossy().to_string()) + .collect(), + missing_dependencies: res + .missing_dependencies + .into_iter() + .map(|d| d.to_string_lossy().to_string()) + .collect(), + assets: res.assets.into_iter().collect(), + id: res.id, + }) + .map_err(|e| Error::new(napi::Status::GenericFailure, format!("{e}"))) + }) + } +} + +#[napi(object)] +pub struct JsExecuteModuleResult { + pub file_dependencies: Vec, + pub context_dependencies: Vec, + pub build_dependencies: Vec, + pub missing_dependencies: Vec, + pub assets: Vec, + pub id: u32, } impl JsCompilation { @@ -436,3 +497,10 @@ impl JsCompilation { Self { inner } } } + +#[napi(object)] +#[derive(Clone, Debug)] +pub struct JsBuildTimeExecutionOption { + pub public_path: Option, + pub base_uri: Option, +} diff --git a/crates/rspack_binding_values/src/module.rs b/crates/rspack_binding_values/src/module.rs index 9a392868309..bb94e235458 100644 --- a/crates/rspack_binding_values/src/module.rs +++ b/crates/rspack_binding_values/src/module.rs @@ -3,7 +3,7 @@ use napi_derive::napi; use rspack_core::Module; use super::{JsCompatSource, ToJsCompatSource}; -use crate::{JsChunk, JsCodegenerationResults}; +use crate::{JsBuildTimeExecutionOption, JsChunk, JsCodegenerationResults}; #[derive(Default)] #[napi(object)] @@ -86,8 +86,11 @@ impl ToJsModule for dyn Module + '_ { #[napi(object)] pub struct JsExecuteModuleArg { pub entry: String, + pub request: String, + pub options: JsBuildTimeExecutionOption, pub runtime_modules: Vec, pub codegen_results: JsCodegenerationResults, + pub id: u32, } #[derive(Default)] diff --git a/crates/rspack_core/src/compiler/compilation.rs b/crates/rspack_core/src/compiler/compilation.rs index aeec0a41411..e635b62f2d7 100644 --- a/crates/rspack_core/src/compiler/compilation.rs +++ b/crates/rspack_core/src/compiler/compilation.rs @@ -34,16 +34,16 @@ use crate::{ tree_shaking::{optimizer, visitor::SymbolRef, BailoutFlag, OptimizeDependencyResult}, AddQueue, AddTask, AddTaskResult, AdditionalChunkRuntimeRequirementsArgs, AdditionalModuleRequirementsArgs, AsyncDependenciesBlock, BoxDependency, BoxModule, BuildQueue, - BuildTask, BuildTaskResult, CacheCount, CacheOptions, Chunk, ChunkByUkey, ChunkContentHash, - ChunkGraph, ChunkGroupByUkey, ChunkGroupUkey, ChunkHashArgs, ChunkKind, ChunkUkey, CleanQueue, - CleanTask, CleanTaskResult, CodeGenerationResult, CodeGenerationResults, CompilationLogger, - CompilationLogging, CompilerOptions, ContentHashArgs, ContextDependency, DependencyId, - DependencyParents, DependencyType, Entry, EntryData, EntryOptions, Entrypoint, ErrorSpan, - FactorizeQueue, FactorizeTask, FactorizeTaskResult, Filename, Logger, Module, - ModuleCreationCallback, ModuleFactory, ModuleFactoryResult, ModuleGraph, ModuleGraphModule, - ModuleIdentifier, ModuleProfile, NormalModuleSource, PathData, ProcessAssetsArgs, - ProcessDependenciesQueue, ProcessDependenciesResult, ProcessDependenciesTask, QueueHandler, - RenderManifestArgs, Resolve, ResolverFactory, RuntimeGlobals, RuntimeModule, + BuildTask, BuildTaskResult, BuildTimeExecutionQueue, BuildTimeExecutionTask, CacheCount, + CacheOptions, Chunk, ChunkByUkey, ChunkContentHash, ChunkGraph, ChunkGroupByUkey, ChunkGroupUkey, + ChunkHashArgs, ChunkKind, ChunkUkey, CleanQueue, CleanTask, CleanTaskResult, + CodeGenerationResults, CompilationLogger, CompilationLogging, CompilerOptions, ContentHashArgs, + ContextDependency, DependencyId, DependencyParents, DependencyType, Entry, EntryData, + EntryOptions, Entrypoint, ErrorSpan, FactorizeQueue, FactorizeTask, FactorizeTaskResult, + Filename, Logger, Module, ModuleCreationCallback, ModuleFactory, ModuleFactoryResult, + ModuleGraph, ModuleGraphModule, ModuleIdentifier, ModuleProfile, NormalModuleSource, PathData, + ProcessAssetsArgs, ProcessDependenciesQueue, ProcessDependenciesResult, ProcessDependenciesTask, + QueueHandler, RenderManifestArgs, Resolve, ResolverFactory, RuntimeGlobals, RuntimeModule, RuntimeRequirementsInTreeArgs, RuntimeSpec, SharedPluginDriver, SourceType, Stats, TaskResult, WorkerTask, }; @@ -538,6 +538,7 @@ impl Compilation { let mut add_queue = AddQueue::new(); let mut build_queue = BuildQueue::new(); let mut process_dependencies_queue = ProcessDependenciesQueue::new(); + let mut buildtime_execution_queue = BuildTimeExecutionQueue::new(); let mut make_failed_dependencies: HashSet = HashSet::default(); let mut make_failed_module: HashSet = HashSet::default(); @@ -577,6 +578,7 @@ impl Compilation { parent_module .and_then(|m| m.as_normal_module()) .and_then(|module| module.name_for_condition()), + true, None, ); }); @@ -585,6 +587,7 @@ impl Compilation { let mut process_deps_time = logger.time_aggregate("module process dependencies task"); let mut factorize_time = logger.time_aggregate("module factorize task"); let mut build_time = logger.time_aggregate("module build task"); + let mut buildtime_execution_time = logger.time_aggregate("buildtime execution task"); let mut build_cache_counter = None; let mut factorize_cache_counter = None; @@ -602,13 +605,18 @@ impl Compilation { &mut add_queue, &mut build_queue, &mut process_dependencies_queue, + &mut buildtime_execution_queue, ); while let Some(task) = factorize_queue.get_task() { + active_task_count += 1; + + // TODO: change when we insert dependency to module_graph + self.module_graph.add_dependency(task.dependency.clone()); + tokio::spawn({ let result_tx = result_tx.clone(); let is_expected_shutdown = is_expected_shutdown.clone(); - active_task_count += 1; async move { if is_expected_shutdown.load(Ordering::SeqCst) { @@ -628,10 +636,10 @@ impl Compilation { let start = build_time.start(); while let Some(task) = build_queue.get_task() { + active_task_count += 1; tokio::spawn({ let result_tx = result_tx.clone(); let is_expected_shutdown = is_expected_shutdown.clone(); - active_task_count += 1; async move { if is_expected_shutdown.load(Ordering::SeqCst) { @@ -714,6 +722,7 @@ impl Compilation { module .as_normal_module() .and_then(|module| module.name_for_condition()), + true, Some(Box::new(move |_| { tx.send(()) .expect("Failed to send callback to process_dependencies"); @@ -744,6 +753,23 @@ impl Compilation { } process_deps_time.end(start); + let start = buildtime_execution_time.start(); + while let Some(task) = buildtime_execution_queue.get_task() { + let BuildTimeExecutionTask { + module, + request, + options, + sender, + } = task; + + if let Err(e) = self.execute_module(module, &request, options, sender.clone()) { + result_tx + .send(Err(e)) + .expect("failed to send error message"); + }; + } + buildtime_execution_time.end(start); + match result_rx.try_recv() { Ok(item) => { if let Ok(item) = &item { @@ -797,6 +823,7 @@ impl Compilation { missing_dependencies, diagnostics, callback, + connect_origin, .. } = task_result; if !diagnostics.is_empty() { @@ -858,6 +885,7 @@ impl Compilation { is_entry, current_profile, callback, + connect_origin, }); tracing::trace!("Module created: {}", &module_identifier); } else { @@ -888,6 +916,7 @@ impl Compilation { plugin_driver: self.plugin_driver.clone(), cache: self.cache.clone(), current_profile, + queue_handler: self.queue_handle.clone(), }); } AddTaskResult::ModuleReused { module, .. } => { @@ -1197,6 +1226,7 @@ impl Compilation { resolve_options: Option>, lazy_visit_modules: std::collections::HashSet, issuer: Option>, + connect_origin: bool, callback: Option, ) { let current_profile = self.options.profile.then(Box::::default); @@ -1228,12 +1258,13 @@ impl Compilation { plugin_driver: self.plugin_driver.clone(), cache: self.cache.clone(), current_profile, + connect_origin, callback, }); } #[instrument(name = "compilation:code_generation", skip(self))] - async fn code_generation(&mut self) -> Result<()> { + fn code_generation(&mut self) -> Result<()> { let logger = self.get_logger("rspack.Compilation"); let mut codegen_cache_counter = match self.options.cache { CacheOptions::Disabled => None, @@ -1249,83 +1280,25 @@ impl Compilation { // Else, share same codegen result for all runtimes. let used_exports_optimization = compilation.options.is_new_tree_shaking() && compilation.options.optimization.used_exports.is_true(); - let results = compilation - .module_graph - .modules() - .par_iter() - .filter(filter_op) - .filter_map(|(module_identifier, module)| { - let runtimes = compilation - .chunk_graph - .get_module_runtimes(*module_identifier, &compilation.chunk_by_ukey); - if runtimes.is_empty() { - return None; - } + let results = compilation.code_generation_modules( + codegen_cache_counter, + used_exports_optimization, + compilation + .module_graph + .modules() + .iter() + .filter(filter_op) + .map(|(id, _)| *id) + .collect::>() + .into_par_iter(), + )?; - let res = compilation - .cache - .code_generate_occasion - .use_cache(module, runtimes, compilation, |module, runtimes| { - let take_length = if used_exports_optimization { - runtimes.len() - } else { - // Only codegen once - 1 - }; - let mut codegen_list = vec![]; - for runtime in runtimes.into_values().take(take_length) { - codegen_list.push(( - module.code_generation(compilation, Some(&runtime), None)?, - runtime, - )); - } - Ok(codegen_list) - }) - .map(|(result, from_cache)| (*module_identifier, result, from_cache)); - Some(res) - }) - .collect::, - bool, - )>, - >>()?; - results - .into_iter() - .for_each(|(module_identifier, item, from_cache)| { - item.into_iter().for_each(|(result, runtime)| { - if let Some(counter) = codegen_cache_counter { - if from_cache { - counter.hit(); - } else { - counter.miss(); - } - } - compilation.code_generated_modules.insert(module_identifier); + results.iter().for_each(|module_identifier| { + compilation + .code_generated_modules + .insert(*module_identifier); + }); - let runtimes = compilation - .chunk_graph - .get_module_runtimes(module_identifier, &compilation.chunk_by_ukey); - let result_id = result.id; - compilation - .code_generation_results - .module_generation_result_map - .insert(result.id, result); - if used_exports_optimization { - compilation - .code_generation_results - .add(module_identifier, runtime, result_id); - } else { - for runtime in runtimes.into_values() { - compilation - .code_generation_results - .add(module_identifier, runtime, result_id); - } - } - }) - }); - // dbg!(&compilation.code_generation_results.map); Ok(()) } @@ -1344,6 +1317,81 @@ impl Compilation { Ok(()) } + pub(crate) fn code_generation_modules( + &mut self, + codegen_cache_counter: &mut Option, + used_exports_optimization: bool, + modules: impl ParallelIterator, + ) -> Result> { + let chunk_graph = &self.chunk_graph; + #[allow(clippy::type_complexity)] + let results = modules + .filter_map(|module_identifier| { + let runtimes = chunk_graph.get_module_runtimes(module_identifier, &self.chunk_by_ukey); + if runtimes.is_empty() { + return None; + } + + let module = self + .module_graph + .module_by_identifier(&module_identifier) + .expect("module should exist"); + let res = self + .cache + .code_generate_occasion + .use_cache(module, runtimes, self, |module, runtimes| { + let take_length = if used_exports_optimization { + runtimes.len() + } else { + // Only codegen once + 1 + }; + let mut codegen_list = vec![]; + for runtime in runtimes.into_values().take(take_length) { + codegen_list.push((module.code_generation(self, Some(&runtime), None)?, runtime)); + } + Ok(codegen_list) + }) + .map(|(result, from_cache)| (module_identifier, result, from_cache)); + Some(res) + }) + .collect::>>()?; + let results = results + .into_iter() + .map(|(module_identifier, item, from_cache)| { + item.into_iter().for_each(|(result, runtime)| { + if let Some(counter) = codegen_cache_counter { + if from_cache { + counter.hit(); + } else { + counter.miss(); + } + } + + let runtimes = chunk_graph.get_module_runtimes(module_identifier, &self.chunk_by_ukey); + let result_id = result.id; + self + .code_generation_results + .module_generation_result_map + .insert(result.id, result); + if used_exports_optimization { + self + .code_generation_results + .add(module_identifier, runtime, result_id); + } else { + for runtime in runtimes.into_values() { + self + .code_generation_results + .add(module_identifier, runtime, result_id); + } + } + }); + module_identifier + }); + + Ok(results.collect()) + } + #[instrument(name = "compilation::create_module_assets", skip_all)] async fn create_module_assets(&mut self, _plugin_driver: SharedPluginDriver) { for (module_identifier, module) in self.module_graph.modules() { @@ -1542,7 +1590,7 @@ impl Compilation { logger.time_end(start); let start = logger.time("code generation"); - self.code_generation().await?; + self.code_generation()?; logger.time_end(start); // if self.options.is_new_tree_shaking() { // debug_all_exports_info!(&self.module_graph); @@ -1642,8 +1690,8 @@ impl Compilation { HashSet::from_iter(entries.chain(async_entries)) } - #[instrument(name = "compilation:process_runtime_requirements", skip_all)] - pub async fn process_runtime_requirements( + #[allow(clippy::unwrap_in_result)] + pub(crate) async fn process_runtime_requirements( &mut self, modules: impl IntoParallelIterator, chunks: impl Iterator, @@ -2029,14 +2077,6 @@ impl Compilation { CompilationLogger::new(name.into(), self.logging.clone()) } - pub fn execute_module(&self, entry: ModuleIdentifier) -> Result> { - let codegen_result = Default::default(); - // TODO - self - .plugin_driver - .execute_module(entry, vec![], &codegen_result) - } - pub fn set_dependency_factory( &mut self, dependency_type: DependencyType, diff --git a/crates/rspack_core/src/compiler/execute_module.rs b/crates/rspack_core/src/compiler/execute_module.rs new file mode 100644 index 00000000000..94fb77651ef --- /dev/null +++ b/crates/rspack_core/src/compiler/execute_module.rs @@ -0,0 +1,435 @@ +use std::sync::atomic::AtomicU32; +use std::{hash::BuildHasherDefault, iter::once, sync::Arc}; + +use rayon::prelude::*; +use rspack_error::Result; +use rspack_identifier::{Identifiable, IdentifierMap}; +use rustc_hash::{FxHashSet as HashSet, FxHasher}; +use tokio::runtime::Handle; +use tokio::sync::mpsc::{unbounded_channel, UnboundedSender}; +use tracing::instrument; + +use crate::{ + cache::Cache, BuildTimeExecutionOption, BuildTimeExecutionTask, Chunk, ChunkByUkey, ChunkGraph, + ChunkKind, CodeGenerationDataAssetInfo, CodeGenerationDataFilename, CodeGenerationResult, + CompilerOptions, Dependency, EntryOptions, Entrypoint, ExecuteModuleResult, FactorizeTask, + LoaderImportDependency, ModuleIdentifier, NormalModuleFactory, QueueHandler, QueueTask, + ResolverFactory, SharedPluginDriver, SourceType, +}; +use crate::{Compilation, CompilationAsset}; +use crate::{Context, RuntimeModule}; + +static EXECUTE_MODULE_ID: AtomicU32 = AtomicU32::new(0); + +impl Compilation { + #[allow(clippy::unwrap_in_result)] + #[instrument(name = "compilation:execute_module")] + pub fn execute_module( + &mut self, + module: ModuleIdentifier, + request: &str, + options: BuildTimeExecutionOption, + result_tx: UnboundedSender>, + ) -> Result<()> { + let id = EXECUTE_MODULE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + + let mut modules: std::collections::HashSet< + rspack_identifier::Identifier, + BuildHasherDefault, + > = HashSet::default(); + let mut queue = vec![module]; + + while let Some(m) = queue.pop() { + modules.insert(m); + let m = self + .module_graph + .module_by_identifier(&m) + .expect("should have module"); + for m in self.module_graph.get_outgoing_connections(m) { + // TODO: handle circle + if !modules.contains(&m.module_identifier) { + queue.push(m.module_identifier); + } + } + } + + tracing::info!( + "modules: {:?}", + &modules.iter().map(|m| m.to_string()).collect::>() + ); + + let mut chunk_graph = ChunkGraph::default(); + + let mut chunk = Chunk::new(Some("build time chunk".into()), ChunkKind::Normal); + + chunk.id = chunk.name.clone(); + chunk.ids = vec![chunk.id.clone().expect("id is set")]; + let runtime = { + let mut runtime = HashSet::default(); + runtime.insert("build time".into()); + runtime + }; + + chunk.runtime = runtime.clone(); + + let mut entrypoint = Entrypoint::new( + crate::ChunkGroupKind::Entrypoint { + initial: true, + options: Box::new(EntryOptions { + name: Some("build time".into()), + runtime: Some("runtime".into()), + chunk_loading: Some(crate::ChunkLoading::Disable), + async_chunks: None, + public_path: options.public_path.clone().map(crate::PublicPath::String), + base_uri: options.base_uri.clone(), + filename: None, + library: None, + }), + }, + crate::ChunkGroupInfo { + chunk_loading: false, + async_chunks: false, + runtime: runtime.clone(), + }, + ); + + // add chunk to this compilation + let chunk_by_ukey = ChunkByUkey::default(); + let old_chunk_by_ukey = std::mem::replace(&mut self.chunk_by_ukey, chunk_by_ukey); + + let chunk = self.chunk_by_ukey.add(chunk); + let chunk_ukey = chunk.ukey; + + chunk_graph.connect_chunk_and_entry_module(chunk.ukey, module, entrypoint.ukey); + entrypoint.connect_chunk(chunk); + entrypoint.set_runtime_chunk(chunk.ukey); + entrypoint.set_entry_point_chunk(chunk.ukey); + + let entry_ukey = entrypoint.ukey; + self.chunk_group_by_ukey.add(entrypoint); + + // Assign ids to modules and modules to the chunk + for m in &modules { + let module = self + .module_graph + .module_by_identifier(m) + .expect("should have module"); + + let id = module.identifier(); + + chunk_graph.add_module(id); + chunk_graph.set_module_id(*m, id.to_string()); + chunk_graph.connect_chunk_and_module(chunk_ukey, *m); + } + + // Webpack uses this trick to make sure process_runtime_requirements access + // the new chunk_graph + // in rspack, if we decouple compilation and chunk_graph, we can't get exclusive ref + // to the chunk_graph in API that receives both compilation and chunk_graph + // + // replace code_generation_results is the same reason + let old_runtime_modules = std::mem::take(&mut self.runtime_modules); + let old_chunk_graph = std::mem::replace(&mut self.chunk_graph, chunk_graph); + + self.code_generation_modules(&mut None, false, modules.par_iter().copied())?; + + Handle::current().block_on(async { + self + .process_runtime_requirements( + modules.clone(), + once(chunk_ukey), + once(chunk_ukey), + self.plugin_driver.clone(), + ) + .await + })?; + + let runtime_modules = self + .chunk_graph + .get_chunk_runtime_modules_iterable(&chunk_ukey) + .copied() + .collect::>(); + + tracing::info!( + "runtime modules: {:?}", + &runtime_modules + .iter() + .map(|m| m.to_string()) + .collect::>() + ); + + for runtime_id in &runtime_modules { + let runtime_module = self + .runtime_modules + .get(runtime_id) + .expect("runtime module exist"); + + let result = CodeGenerationResult::default().with_javascript(runtime_module.generate(self)); + let result_id = result.id; + + self + .code_generation_results + .module_generation_result_map + .insert(result.id, result); + self + .code_generation_results + .add(*runtime_id, runtime.clone(), result_id); + } + + let codegen_results = self.code_generation_results.clone(); + let exports = self.plugin_driver.execute_module( + module, + request, + &options, + runtime_modules.iter().cloned().collect(), + &codegen_results, + id, + ); + + let execute_result = match exports { + Ok(_) => { + let mut result = modules + .iter() + .fold(ExecuteModuleResult::default(), |mut res, m| { + let module = self + .module_graph + .module_by_identifier(m) + .expect("unreachable"); + + let build_info = &module.build_info(); + if let Some(info) = build_info { + res + .file_dependencies + .extend(info.file_dependencies.iter().cloned()); + res + .context_dependencies + .extend(info.context_dependencies.iter().cloned()); + res + .missing_dependencies + .extend(info.missing_dependencies.iter().cloned()); + res + .build_dependencies + .extend(info.build_dependencies.iter().cloned()); + res.assets.extend(info.asset_filenames.iter().cloned()); + } + res + }); + + result.id = id; + + modules.iter().for_each(|m| { + let codegen_result = codegen_results.get(m, Some(&runtime)); + + if let Some(source) = codegen_result.get(&SourceType::Asset) + && let Some(filename) = codegen_result.data.get::() + && let Some(asset_info) = codegen_result.data.get::() + { + let filename = filename.filename(); + self.emit_asset( + filename.to_owned(), + CompilationAsset::new(Some(source.clone()), asset_info.inner().clone()), + ); + result.assets.insert(filename.to_owned()); + } + }); + + Ok(result) + } + Err(e) => Err(e), + }; + + // clear side effects stuff we caused + self.clear_execute_module_effects(old_chunk_graph, old_chunk_by_ukey, old_runtime_modules); + + self.chunk_group_by_ukey.remove(&entry_ukey); + + result_tx.send(execute_result).expect("todo"); + Ok(()) + } + + #[instrument(name = "compilation::import_module")] + pub async fn import_module( + &self, + request: String, + public_path: Option, + base_uri: Option, + original_module_identifier: Option, + original_module_context: Option>, + ) -> Result { + Self::import_module_impl( + self + .queue_handle + .clone() + .expect("call import module without queueHandler"), + self.resolver_factory.clone(), + self.options.clone(), + self.plugin_driver.clone(), + self.cache.clone(), + request, + public_path, + base_uri, + original_module_identifier, + original_module_context, + ) + .await + } + + #[allow(clippy::too_many_arguments)] + #[instrument(name = "compilation::import_module_impl")] + pub async fn import_module_impl( + queue_handler: QueueHandler, + resolver_factory: Arc, + options: Arc, + plugin_driver: SharedPluginDriver, + cache: Arc, + + request: String, + public_path: Option, + base_uri: Option, + original_module_identifier: Option, + original_module_context: Option>, + ) -> Result { + let (tx, mut rx) = unbounded_channel::<(ModuleIdentifier, Option>)>(); + + let dep = LoaderImportDependency::new(request.clone()); + let dep_id = *dep.id(); + + let tx_clone = tx.clone(); + + queue_handler.add_task(crate::QueueTask::Factorize(Box::new(FactorizeTask { + module_factory: Arc::new(NormalModuleFactory::new( + options.clone(), + resolver_factory.clone(), + plugin_driver.clone(), + cache.clone(), + )), + original_module_source: None, + original_module_identifier, + original_module_context, + issuer: None, + dependency: Box::new(dep), + dependencies: vec![dep_id], + is_entry: false, + resolve_options: Some(Box::new(options.resolve.clone())), + resolver_factory: resolver_factory.clone(), + loader_resolver_factory: resolver_factory.clone(), + options: options.clone(), + lazy_visit_modules: Default::default(), + plugin_driver: plugin_driver.clone(), + cache: cache.clone(), + current_profile: None, + connect_origin: false, + callback: Some(Box::new(move |m| { + tx_clone + .send((m.identifier(), None)) + .expect("failed to send entry module of buildtime execution modules") + })), + })))?; + + let mut to_be_completed = 0; + let mut completed = 0; + let mut waited = vec![]; + let mut modules = HashSet::default(); + + let mut entry = None; + + // wait for every child to be built + loop { + if let Some((module, deps)) = rx.recv().await { + if let Some(deps) = deps { + completed += 1; + modules.insert(module); + + for dep in deps { + if !modules.contains(&dep) { + waited.push(dep); + to_be_completed += 1; + } + } + } else { + entry = Some(module); + waited.push(module); + to_be_completed += 1; + } + + while let Some(module) = waited.pop() { + let tx_clone = tx.clone(); + + queue_handler.wait_for( + crate::WaitTask::ProcessDependencies(module), + Box::new(move |module, compilation| { + let m = compilation + .module_graph + .module_by_identifier(&module) + .expect("todo"); + + tx_clone + .send(( + module, + Some( + compilation + .module_graph + .get_outgoing_connections(m) + .into_iter() + .map(|conn| conn.module_identifier) + .collect(), + ), + )) + .expect("todo"); + }), + ); + } + } else { + unreachable!(); + } + + if to_be_completed == completed { + break; + } + } + + let (tx, mut rx) = unbounded_channel(); + + queue_handler.add_task(QueueTask::BuildTimeExecution(Box::new( + BuildTimeExecutionTask { + module: entry.expect("should has entry module"), + request, + options: BuildTimeExecutionOption { + public_path, + base_uri, + }, + sender: tx, + }, + )))?; + + rx.recv().await.unwrap_or_else(|| { + Err( + rspack_error::InternalError::new( + "failed on calling importModule at execution stage".into(), + rspack_error::RspackSeverity::Error, + ) + .into(), + ) + }) + } + + // modules that execute during build are called build-time modules. + // executeModule will invoke some modules' codegen and process their runtime, + // however during this process, some plugins might access compilation.chunk_graph + // or compilation.runtime_modules, and modify them, for example add runtimeModule + // to compilation.runtime_modules, but those runtime modules are only needed by + // build-time modules, and should not present in the output, thus we should + // clear them after we finish executing build-time modules + // compare to webpack, we have extra items to restore: runtime_modules, because webpack + // store runtimeModules in ChunkGraph, but we store them in compilation + fn clear_execute_module_effects( + &mut self, + chunk_graph: ChunkGraph, + chunk_by_ukey: ChunkByUkey, + runtime_modules: IdentifierMap>, + ) { + self.chunk_graph = chunk_graph; + self.chunk_by_ukey = chunk_by_ukey; + self.runtime_modules = runtime_modules; + } +} diff --git a/crates/rspack_core/src/compiler/mod.rs b/crates/rspack_core/src/compiler/mod.rs index 0362352270d..55b55e82f5c 100644 --- a/crates/rspack_core/src/compiler/mod.rs +++ b/crates/rspack_core/src/compiler/mod.rs @@ -1,4 +1,5 @@ mod compilation; +mod execute_module; mod hmr; mod make; mod queue; diff --git a/crates/rspack_core/src/compiler/queue.rs b/crates/rspack_core/src/compiler/queue.rs index 6f32b351b94..9e5155e27f5 100644 --- a/crates/rspack_core/src/compiler/queue.rs +++ b/crates/rspack_core/src/compiler/queue.rs @@ -13,7 +13,7 @@ use crate::{ ModuleGraph, ModuleGraphModule, ModuleIdentifier, ModuleProfile, Resolve, ResolverFactory, SharedPluginDriver, WorkerQueue, }; -use crate::{BoxModule, DependencyId, ExportInfo, ExportsInfo, UsageState}; +use crate::{BoxModule, DependencyId, ExecuteModuleResult, ExportInfo, ExportsInfo, UsageState}; #[derive(Debug)] pub enum TaskResult { @@ -47,6 +47,7 @@ pub struct FactorizeTask { pub plugin_driver: SharedPluginDriver, pub cache: Arc, pub current_profile: Option>, + pub connect_origin: bool, #[derivative(Debug = "ignore")] pub callback: Option, } @@ -77,6 +78,7 @@ pub struct FactorizeTaskResult { pub diagnostics: Vec, #[derivative(Debug = "ignore")] pub callback: Option, + pub connect_origin: bool, } impl FactorizeTaskResult { @@ -147,6 +149,7 @@ impl WorkerTask for FactorizeTask { context_dependencies: Default::default(), missing_dependencies: Default::default(), diagnostics: Default::default(), + connect_origin: self.connect_origin, callback: self.callback, }; @@ -220,6 +223,7 @@ pub struct AddTask { pub dependencies: Vec, pub is_entry: bool, pub current_profile: Option>, + pub connect_origin: bool, #[derivative(Debug = "ignore")] pub callback: Option, } @@ -241,7 +245,7 @@ impl AddTask { current_profile.mark_integration_start(); } - if self.module.as_self_module().is_some() { + if self.module.as_self_module().is_some() && self.connect_origin { let issuer = self .module_graph_module .get_issuer() @@ -262,10 +266,11 @@ impl AddTask { let module_identifier = self.module.identifier(); - if compilation - .module_graph - .module_graph_module_by_identifier(&module_identifier) - .is_some() + if self.connect_origin + && compilation + .module_graph + .module_graph_module_by_identifier(&module_identifier) + .is_some() { set_resolved_module( &mut compilation.module_graph, @@ -287,12 +292,14 @@ impl AddTask { .module_graph .add_module_graph_module(*self.module_graph_module); - set_resolved_module( - &mut compilation.module_graph, - self.original_module_identifier, - self.dependencies, - module_identifier, - )?; + if self.connect_origin { + set_resolved_module( + &mut compilation.module_graph, + self.original_module_identifier, + self.dependencies, + module_identifier, + )?; + } if self.is_entry { compilation @@ -337,6 +344,7 @@ pub struct BuildTask { pub plugin_driver: SharedPluginDriver, pub cache: Arc, pub current_profile: Option>, + pub queue_handler: Option, } #[derive(Debug)] @@ -378,6 +386,9 @@ impl WorkerTask for BuildTask { module: module.identifier(), module_context: module.as_normal_module().and_then(|m| m.get_context()), module_source_map_kind: module.get_source_map_kind().clone(), + queue_handler: self.queue_handler.clone(), + plugin_driver: plugin_driver.clone(), + cache: cache.clone(), }, plugin_driver: plugin_driver.clone(), compiler_options: &compiler_options, @@ -444,6 +455,22 @@ pub struct ProcessDependenciesResult { pub type ProcessDependenciesQueue = WorkerQueue; +#[derive(Clone, Debug)] +pub struct BuildTimeExecutionOption { + pub public_path: Option, + pub base_uri: Option, +} + +#[derive(Debug, Clone)] +pub struct BuildTimeExecutionTask { + pub module: ModuleIdentifier, + pub request: String, + pub options: BuildTimeExecutionOption, + pub sender: UnboundedSender>, +} + +pub type BuildTimeExecutionQueue = WorkerQueue; + pub struct CleanTask { pub module_identifier: ModuleIdentifier, } @@ -506,6 +533,8 @@ pub enum QueueTask { Add(Box), Build(Box), ProcessDependencies(Box), + BuildTimeExecution(Box), + Subscription(Box), } @@ -595,6 +624,7 @@ impl QueueHandlerProcessor { add_queue: &mut AddQueue, build_queue: &mut BuildQueue, process_dependencies_queue: &mut ProcessDependenciesQueue, + buildtime_execution_queue: &mut BuildTimeExecutionQueue, ) { while let Ok(task) = self.receiver.try_recv() { match task { @@ -610,6 +640,9 @@ impl QueueHandlerProcessor { QueueTask::ProcessDependencies(task) => { process_dependencies_queue.add_task(*task); } + QueueTask::BuildTimeExecution(task) => { + buildtime_execution_queue.add_task(*task); + } QueueTask::Subscription(subscription) => { let Subscription { task, callback } = *subscription; let (bucket, key) = Self::get_bucket_and_key(task); diff --git a/crates/rspack_core/src/dependency/dependency_category.rs b/crates/rspack_core/src/dependency/dependency_category.rs index fc095815753..56198b5fa1a 100644 --- a/crates/rspack_core/src/dependency/dependency_category.rs +++ b/crates/rspack_core/src/dependency/dependency_category.rs @@ -12,6 +12,7 @@ pub enum DependencyCategory { CssCompose, Wasm, Worker, + LoaderImport, } impl From<&str> for DependencyCategory { @@ -41,6 +42,7 @@ impl DependencyCategory { DependencyCategory::CssCompose => "css-compose", DependencyCategory::Wasm => "wasm", DependencyCategory::Worker => "worker", + DependencyCategory::LoaderImport => "loader import", } } } diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index dd6867ff951..1a00e1182d1 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -88,6 +88,7 @@ pub enum DependencyType { ConsumeSharedFallback, /// Webpack is included WebpackIsIncluded, + LoaderImport, Custom(Box), // TODO it will increase large layout size } @@ -128,6 +129,7 @@ impl DependencyType { DependencyType::WasmImport => Cow::Borrowed("wasm import"), DependencyType::WasmExportImported => Cow::Borrowed("wasm export imported"), DependencyType::StaticExports => Cow::Borrowed("static exports"), + DependencyType::LoaderImport => Cow::Borrowed("loader import"), DependencyType::Custom(ty) => Cow::Owned(format!("custom {ty}")), DependencyType::ExportInfoApi => Cow::Borrowed("export info api"), // TODO: mode diff --git a/crates/rspack_core/src/dependency/loader_import_dependency.rs b/crates/rspack_core/src/dependency/loader_import_dependency.rs new file mode 100644 index 00000000000..12822d2010f --- /dev/null +++ b/crates/rspack_core/src/dependency/loader_import_dependency.rs @@ -0,0 +1,53 @@ +use crate::{ + AsContextDependency, AsDependencyTemplate, Dependency, DependencyId, ModuleDependency, +}; + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct LoaderImportDependency { + request: String, + id: DependencyId, +} + +impl LoaderImportDependency { + pub fn new(request: String) -> Self { + Self { + request, + id: DependencyId::new(), + } + } +} + +impl AsDependencyTemplate for LoaderImportDependency {} +impl AsContextDependency for LoaderImportDependency {} + +impl Dependency for LoaderImportDependency { + fn dependency_debug_name(&self) -> &'static str { + "LoaderImportDependency" + } + + fn id(&self) -> &crate::DependencyId { + &self.id + } + + fn dependency_type(&self) -> &crate::DependencyType { + &crate::DependencyType::LoaderImport + } + + fn category(&self) -> &crate::DependencyCategory { + &crate::DependencyCategory::LoaderImport + } +} + +impl ModuleDependency for LoaderImportDependency { + fn request(&self) -> &str { + &self.request + } + + fn user_request(&self) -> &str { + &self.request + } + + fn set_request(&mut self, request: String) { + self.request = request + } +} diff --git a/crates/rspack_core/src/dependency/mod.rs b/crates/rspack_core/src/dependency/mod.rs index 835aa57eb87..debc305b7ff 100644 --- a/crates/rspack_core/src/dependency/mod.rs +++ b/crates/rspack_core/src/dependency/mod.rs @@ -9,6 +9,7 @@ mod dependency_trait; mod dependency_type; mod entry; mod import_dependency_trait; +mod loader_import_dependency; mod module_dependency; mod runtime_requirements_dependency; mod runtime_template; @@ -27,6 +28,7 @@ pub use dependency_trait::*; pub use dependency_type::DependencyType; pub use entry::*; pub use import_dependency_trait::ImportDependencyTrait; +pub use loader_import_dependency::LoaderImportDependency; pub use module_dependency::*; pub use runtime_requirements_dependency::RuntimeRequirementsDependency; pub use runtime_template::*; diff --git a/crates/rspack_core/src/loader/loader_runner.rs b/crates/rspack_core/src/loader/loader_runner.rs index e55b7679a20..2ce1a9d5928 100644 --- a/crates/rspack_core/src/loader/loader_runner.rs +++ b/crates/rspack_core/src/loader/loader_runner.rs @@ -2,8 +2,12 @@ use std::sync::Arc; pub use rspack_loader_runner::{run_loaders, Content, Loader, LoaderContext}; use rspack_util::source_map::SourceMapKind; +use rustc_hash::FxHashSet; -use crate::{CompilerOptions, Context, ModuleIdentifier, ResolverFactory}; +use crate::{ + cache::Cache, Compilation, CompilerOptions, Context, ModuleIdentifier, QueueHandler, + ResolverFactory, SharedPluginDriver, +}; #[derive(Debug, Clone)] pub struct CompilerContext { @@ -12,6 +16,49 @@ pub struct CompilerContext { pub module: ModuleIdentifier, // current module pub module_context: Option>, // current module context pub module_source_map_kind: SourceMapKind, + + pub queue_handler: Option, + pub plugin_driver: SharedPluginDriver, + pub cache: Arc, +} + +#[derive(Debug, Default)] +pub struct ExecuteModuleResult { + pub file_dependencies: FxHashSet, + pub context_dependencies: FxHashSet, + pub missing_dependencies: FxHashSet, + pub build_dependencies: FxHashSet, + pub assets: FxHashSet, + pub id: u32, +} + +impl CompilerContext { + pub async fn import_module( + &self, + request: String, + public_path: Option, + base_uri: Option, + ) -> rspack_error::Result { + if self.queue_handler.is_none() { + return Err(rspack_error::error!( + "use import_module without queue_handler" + )); + } + + Compilation::import_module_impl( + self.queue_handler.clone().expect("unreachable"), + self.resolver_factory.clone(), + self.options.clone(), + self.plugin_driver.clone(), + self.cache.clone(), + request, + public_path, + base_uri, + Some(self.module), + self.module_context.clone(), + ) + .await + } } pub type LoaderRunnerContext = CompilerContext; diff --git a/crates/rspack_core/src/logger.rs b/crates/rspack_core/src/logger.rs index b89354f2ce7..0ceeeff5c7f 100644 --- a/crates/rspack_core/src/logger.rs +++ b/crates/rspack_core/src/logger.rs @@ -270,6 +270,7 @@ impl StartTimeAggregate { } } +#[derive(Debug)] pub struct CacheCount { label: &'static str, hit: u32, diff --git a/crates/rspack_core/src/plugin/api.rs b/crates/rspack_core/src/plugin/api.rs index cbf993f1a84..8dbfa9fcf78 100644 --- a/crates/rspack_core/src/plugin/api.rs +++ b/crates/rspack_core/src/plugin/api.rs @@ -5,17 +5,18 @@ use rspack_error::{IntoTWithDiagnosticArray, Result, TWithDiagnosticArray}; use rspack_hash::RspackHashDigest; use rspack_loader_runner::{Content, LoaderContext, ResourceData}; use rspack_sources::BoxSource; +use tokio::sync::mpsc::UnboundedSender; use crate::{ AdditionalChunkRuntimeRequirementsArgs, AdditionalModuleRequirementsArgs, AssetEmittedArgs, - AssetInfo, BoxLoader, BoxModule, Chunk, ChunkAssetArgs, ChunkHashArgs, CodeGenerationResults, - Compilation, CompilationArgs, CompilationParams, CompilerOptions, ContentHashArgs, DoneArgs, - FactorizeArgs, JsChunkHashArgs, LoaderRunnerContext, MakeParam, Module, ModuleFactoryResult, - ModuleIdentifier, ModuleType, NormalModule, NormalModuleAfterResolveArgs, - NormalModuleBeforeResolveArgs, NormalModuleCreateData, OptimizeChunksArgs, ParserAndGenerator, - PluginContext, ProcessAssetsArgs, RenderArgs, RenderChunkArgs, RenderManifestArgs, - RenderModuleContentArgs, RenderStartupArgs, Resolver, RuntimeModule, - RuntimeRequirementsInTreeArgs, SourceType, ThisCompilationArgs, + AssetInfo, BoxLoader, BoxModule, BuildTimeExecutionOption, Chunk, ChunkAssetArgs, ChunkHashArgs, + CodeGenerationResults, Compilation, CompilationArgs, CompilationParams, CompilerOptions, + ContentHashArgs, DependencyId, DoneArgs, FactorizeArgs, JsChunkHashArgs, LoaderRunnerContext, + MakeParam, Module, ModuleFactoryResult, ModuleIdentifier, ModuleType, NormalModule, + NormalModuleAfterResolveArgs, NormalModuleBeforeResolveArgs, NormalModuleCreateData, + OptimizeChunksArgs, ParserAndGenerator, PluginContext, ProcessAssetsArgs, RenderArgs, + RenderChunkArgs, RenderManifestArgs, RenderModuleContentArgs, RenderStartupArgs, Resolver, + RuntimeModule, RuntimeRequirementsInTreeArgs, SourceType, ThisCompilationArgs, }; // use anyhow::{Context, Result}; @@ -525,13 +526,25 @@ pub trait Plugin: Debug + Send + Sync { Ok(()) } + fn prepare_execute_module( + &self, + _id: DependencyId, + _options: &BuildTimeExecutionOption, + _import_module_informer: UnboundedSender>, + ) -> Result<()> { + Ok(()) + } + fn execute_module( &self, _entry: ModuleIdentifier, + _request: &str, + _options: &BuildTimeExecutionOption, _runtime_modules: Vec, _codegen_results: &CodeGenerationResults, - ) -> Result> { - Ok(None) + _id: u32, + ) -> Result<()> { + Ok(()) } } diff --git a/crates/rspack_core/src/plugin/plugin_driver.rs b/crates/rspack_core/src/plugin/plugin_driver.rs index 8574eafdc73..c9856fea5d4 100644 --- a/crates/rspack_core/src/plugin/plugin_driver.rs +++ b/crates/rspack_core/src/plugin/plugin_driver.rs @@ -6,16 +6,17 @@ use std::{ use rspack_error::{Diagnostic, Result, TWithDiagnosticArray}; use rspack_loader_runner::{LoaderContext, ResourceData}; use rustc_hash::FxHashMap as HashMap; +use tokio::sync::mpsc::UnboundedSender; use tracing::instrument; use crate::{ AdditionalChunkRuntimeRequirementsArgs, AdditionalModuleRequirementsArgs, ApplyContext, - AssetEmittedArgs, BoxLoader, BoxModule, BoxedParserAndGeneratorBuilder, Chunk, ChunkAssetArgs, - ChunkContentHash, ChunkHashArgs, CodeGenerationResults, Compilation, CompilationArgs, - CompilationParams, CompilerOptions, Content, ContentHashArgs, DoneArgs, FactorizeArgs, - JsChunkHashArgs, LoaderRunnerContext, MakeParam, Module, ModuleIdentifier, ModuleType, - NormalModule, NormalModuleAfterResolveArgs, NormalModuleBeforeResolveArgs, - NormalModuleCreateData, OptimizeChunksArgs, Plugin, + AssetEmittedArgs, BoxLoader, BoxModule, BoxedParserAndGeneratorBuilder, BuildTimeExecutionOption, + Chunk, ChunkAssetArgs, ChunkContentHash, ChunkHashArgs, CodeGenerationResults, Compilation, + CompilationArgs, CompilationParams, CompilerOptions, Content, ContentHashArgs, DependencyId, + DoneArgs, FactorizeArgs, JsChunkHashArgs, LoaderRunnerContext, MakeParam, Module, + ModuleIdentifier, ModuleType, NormalModule, NormalModuleAfterResolveArgs, + NormalModuleBeforeResolveArgs, NormalModuleCreateData, OptimizeChunksArgs, Plugin, PluginAdditionalChunkRuntimeRequirementsOutput, PluginAdditionalModuleRequirementsOutput, PluginBuildEndHookOutput, PluginChunkHashHookOutput, PluginCompilationHookOutput, PluginContext, PluginFactorizeHookOutput, PluginJsChunkHashHookOutput, PluginMakeHookOutput, @@ -773,21 +774,40 @@ impl PluginDriver { Ok(()) } + pub fn prepare_execute_module( + &self, + dep: DependencyId, + options: &BuildTimeExecutionOption, + import_module_informer: UnboundedSender>, + ) -> Result<()> { + for plugin in &self.plugins { + plugin.prepare_execute_module(dep, options, import_module_informer.clone())?; + } + + Ok(()) + } + #[instrument(name = "plugin:execute_module", skip_all)] pub fn execute_module( &self, entry: ModuleIdentifier, + request: &str, + options: &BuildTimeExecutionOption, runtime_modules: Vec, codegen_results: &CodeGenerationResults, - ) -> Result> { + id: u32, + ) -> Result<()> { for plugin in &self.plugins { - if let Some(exports) = - plugin.execute_module(entry, runtime_modules.clone(), codegen_results)? - { - return Ok(Some(exports)); - } + plugin.execute_module( + entry, + request, + options, + runtime_modules.clone(), + codegen_results, + id, + )? } - Ok(None) + Ok(()) } } diff --git a/crates/rspack_core/src/tree_shaking/mod.rs b/crates/rspack_core/src/tree_shaking/mod.rs index 8d8e2856d61..2aebb0c29d4 100644 --- a/crates/rspack_core/src/tree_shaking/mod.rs +++ b/crates/rspack_core/src/tree_shaking/mod.rs @@ -68,6 +68,7 @@ bitflags::bitflags! { const DYNAMIC_IMPORT = 1 << 3; const CONTEXT_MODULE = 1 << 4; const CONTAINER_EXPOSED = 1 << 5; + const BUILDTIME_EXECUTION = 1 << 6; } } diff --git a/crates/rspack_database/src/database.rs b/crates/rspack_database/src/database.rs index 05d9d9a707c..afd6283f65b 100644 --- a/crates/rspack_database/src/database.rs +++ b/crates/rspack_database/src/database.rs @@ -114,9 +114,9 @@ impl Database { } impl Database { - pub fn add(&mut self, item: Item) { + pub fn add(&mut self, item: Item) -> &mut Item { debug_assert!(self.inner.get(&item.ukey()).is_none()); let ukey = item.ukey(); - self.inner.insert(ukey, item); + self.inner.entry(ukey).or_insert(item) } } diff --git a/crates/rspack_loader_swc/tests/fixtures.rs b/crates/rspack_loader_swc/tests/fixtures.rs index 09f3683d220..aae0a5234b9 100644 --- a/crates/rspack_loader_swc/tests/fixtures.rs +++ b/crates/rspack_loader_swc/tests/fixtures.rs @@ -6,8 +6,8 @@ use std::{ }; use rspack_core::{ - run_loaders, CompilerContext, CompilerOptions, Loader, LoaderRunnerContext, ResourceData, - SideEffectOption, + run_loaders, CompilerContext, CompilerOptions, Loader, LoaderRunnerContext, PluginDriver, + ResourceData, SideEffectOption, }; use rspack_loader_swc::{SwcLoader, SwcLoaderJsOptions}; use rspack_testing::{fixture, test_fixture}; @@ -27,6 +27,76 @@ async fn loader_test(actual: impl AsRef, expected: impl AsRef) { plugin_path.to_string_lossy().to_string(), json!(null), )]); + + let compiler_options = CompilerOptions { + bail: false, + context: rspack_core::Context::default(), + dev_server: rspack_core::DevServerOptions::default(), + mode: rspack_core::Mode::None, + output: rspack_core::OutputOptions { + clean: false, + path: Default::default(), + public_path: Default::default(), + filename: rspack_core::Filename::from_str("").expect("TODO:"), + asset_module_filename: rspack_core::Filename::from_str("").expect("TODO:"), + wasm_loading: rspack_core::WasmLoading::Disable, + webassembly_module_filename: rspack_core::Filename::from_str("").expect("TODO:"), + chunk_filename: rspack_core::Filename::from_str("").expect("TODO:"), + cross_origin_loading: rspack_core::CrossOriginLoading::Disable, + unique_name: Default::default(), + chunk_loading: rspack_core::ChunkLoading::Enable(rspack_core::ChunkLoadingType::Jsonp), + chunk_loading_global: "webpackChunkwebpack".to_string(), + css_chunk_filename: rspack_core::Filename::from_str("").expect("TODO:"), + css_filename: rspack_core::Filename::from_str("").expect("TODO:"), + hot_update_chunk_filename: rspack_core::Filename::from_str("").expect("Should exist"), + hot_update_main_filename: rspack_core::Filename::from_str("").expect("Should exist"), + hot_update_global: "webpackHotUpdate".to_string(), + library: None, + enabled_library_types: None, + strict_module_error_handling: false, + global_object: "self".to_string(), + import_function_name: "import".to_string(), + iife: true, + module: false, + trusted_types: None, + source_map_filename: rspack_core::Filename::from_str("./a.map.js").expect("TODO:"), + hash_function: rspack_core::HashFunction::Xxhash64, + hash_digest: rspack_core::HashDigest::Hex, + hash_digest_length: 16, + hash_salt: rspack_core::HashSalt::None, + async_chunks: true, + worker_chunk_loading: rspack_core::ChunkLoading::Enable( + rspack_core::ChunkLoadingType::ImportScripts, + ), + worker_wasm_loading: rspack_core::WasmLoading::Disable, + worker_public_path: String::new(), + script_type: String::from("false"), + }, + target: rspack_core::Target::new(&vec![String::from("web")]).expect("TODO:"), + resolve: rspack_core::Resolve::default(), + resolve_loader: rspack_core::Resolve::default(), + builtins: Default::default(), + module: Default::default(), + stats: Default::default(), + cache: Default::default(), + snapshot: Default::default(), + experiments: Default::default(), + node: Default::default(), + optimization: rspack_core::Optimization { + remove_available_modules: false, + side_effects: SideEffectOption::False, + provided_exports: Default::default(), + used_exports: Default::default(), + inner_graph: Default::default(), + mangle_exports: Default::default(), + concatenate_modules: Default::default(), + }, + profile: false, + }; + + let (plugin_driver, compiler_options) = + PluginDriver::new(compiler_options, vec![], Default::default()); + let (result, _) = run_loaders( &[Arc::new(SwcLoader::new(options)) as Arc>], &ResourceData::new(actual_path.to_string_lossy().to_string(), actual_path), @@ -101,6 +171,9 @@ async fn loader_test(actual: impl AsRef, expected: impl AsRef) { module: "".into(), module_context: None, module_source_map_kind: SourceMapKind::SourceMap, + queue_handler: Default::default(), + plugin_driver, + cache: Arc::new(rspack_core::cache::Cache::new(compiler_options)), }, ) .await diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs index 7796d80e82c..83ddb588779 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_specifier_dependency.rs @@ -70,7 +70,11 @@ impl HarmonyImportSpecifierDependency { // TODO move export_info pub fn check_used(&self, reference_mgm: &ModuleGraphModule, compilation: &Compilation) -> bool { - if compilation.options.builtins.tree_shaking.is_false() { + if compilation.options.builtins.tree_shaking.is_false() + || compilation + .bailout_module_identifiers + .contains_key(&reference_mgm.module_identifier) + { return true; } if !compilation diff --git a/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs index 4c664ec6242..69cfe84ce08 100644 --- a/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/module_concatenation_plugin.rs @@ -798,6 +798,9 @@ impl Plugin for ModuleConcatenationPlugin { module: new_module.id(), module_context: None, module_source_map_kind: rspack_util::source_map::SourceMapKind::None, + cache: compilation.cache.clone(), + queue_handler: compilation.queue_handle.clone(), + plugin_driver: compilation.plugin_driver.clone(), }, plugin_driver: compilation.plugin_driver.clone(), compiler_options: &compilation.options, diff --git a/packages/rspack/src/Compilation.ts b/packages/rspack/src/Compilation.ts index e195b1786da..29c4e66df9a 100644 --- a/packages/rspack/src/Compilation.ts +++ b/packages/rspack/src/Compilation.ts @@ -82,7 +82,7 @@ export interface KnownCreateStatsOptionsContext { } export interface ExecuteModuleArgument { - result: CodeGenerationResult; + codeGenerationResult: CodeGenerationResult; moduleObject: { id: string; exports: any; diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 48ffb2bc207..5577a53c5e4 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -49,6 +49,7 @@ import { tryRunOrWebpackError } from "./lib/HookWebpackError"; import { CodeGenerationResult } from "./Module"; import { canInherentFromParent } from "./builtin-plugin/base"; import { CreateModuleData } from "@rspack/binding"; +import ExecuteModulePlugin from "./ExecuteModulePlugin"; class Compiler { #_instance?: binding.Rspack; @@ -121,6 +122,8 @@ class Compiler { #disabledHooks: string[]; parentCompilation?: Compilation; + #moduleExecutionResultsMap: Map; + constructor(context: string, options: RspackOptionsNormalized) { this.outputFileSystem = fs; this.options = options; @@ -180,6 +183,9 @@ class Compiler { this.modifiedFiles = undefined; this.removedFiles = undefined; this.#disabledHooks = []; + this.#moduleExecutionResultsMap = new Map(); + + new ExecuteModulePlugin().apply(this); } /** @@ -883,14 +889,20 @@ class Compiler { return; } - #executeModule({ + async #executeModule({ entry, + request, + options, runtimeModules, - codegenResults + codegenResults, + id }: { entry: string; + request: string; + options: { publicPath?: string; baseUri?: string }; runtimeModules: string[]; codegenResults: binding.JsCodegenerationResults; + id: number; }) { const __webpack_require__: any = (id: string) => { const cached = moduleCache[id]; @@ -922,7 +934,10 @@ class Compiler { tryRunOrWebpackError( () => this.compilation.hooks.executeModule.call( - { result: new CodeGenerationResult(result), moduleObject }, + { + codeGenerationResult: new CodeGenerationResult(result), + moduleObject + }, { __webpack_require__ } ), "Compilation.hooks.executeModule" @@ -945,9 +960,9 @@ class Compiler { __webpack_require__(runtimeModule); } - exports = __webpack_require__(entry); + const executeResult = __webpack_require__(entry); - return JSON.stringify(exports); + this.#moduleExecutionResultsMap.set(id, executeResult); } #compilation(native: binding.JsCompilation) { @@ -1158,6 +1173,10 @@ class Compiler { __internal__registerBuiltinPlugin(plugin: binding.BuiltinPlugin) { this.builtinPlugins.push(plugin); } + + __internal__getModuleExecutionResult(id: number) { + return this.#moduleExecutionResultsMap.get(id); + } } export { Compiler }; diff --git a/packages/rspack/src/ExecuteModulePlugin.ts b/packages/rspack/src/ExecuteModulePlugin.ts new file mode 100644 index 00000000000..aff8066892d --- /dev/null +++ b/packages/rspack/src/ExecuteModulePlugin.ts @@ -0,0 +1,57 @@ +import { RuntimeGlobals } from "."; +import type { Compiler } from "./Compiler"; +import vm from "node:vm"; + +export default class ExecuteModulePlugin { + constructor() {} + + apply(compiler: Compiler) { + compiler.hooks.compilation.tap("executeModule", compilation => { + compilation.hooks.executeModule.tap( + "executeModule", + (options, context) => { + const moduleObject = options.moduleObject; + const source = options.codeGenerationResult.get("javascript"); + try { + const fn = vm.runInThisContext( + `(function(module, __webpack_exports__, ${RuntimeGlobals.require}) {\n${source}\n})`, + { + filename: moduleObject.id + } + ); + + fn.call( + moduleObject.exports, + moduleObject, + moduleObject.exports, + context.__webpack_require__ + ); + } catch (e: any) { + let err = e instanceof Error ? e : new Error(e); + + err.stack += printGeneratedCodeForStack(moduleObject.id, source); + throw err; + } + } + ); + }); + } +} +const printGeneratedCodeForStack = (moduleId: string, code: string) => { + const lines = code.split("\n"); + const n = `${lines.length}`.length; + return `\n\nGenerated code for ${moduleId}\n${lines + .map( + /** + * @param {string} line the line + * @param {number} i the index + * @param {string[]} lines the lines + * @returns {string} the line with line number + */ + (line, i, lines) => { + const iStr = `${i + 1}`; + return `${" ".repeat(n - iStr.length)}${iStr} | ${line}`; + } + ) + .join("\n")}`; +}; diff --git a/packages/rspack/src/Module.ts b/packages/rspack/src/Module.ts index 583a376ca38..cf54cd8e11e 100644 --- a/packages/rspack/src/Module.ts +++ b/packages/rspack/src/Module.ts @@ -38,9 +38,14 @@ export class Module { export class CodeGenerationResult { #inner: JsCodegenerationResult; + constructor(result: JsCodegenerationResult) { this.#inner = result; } + + get(sourceType: string) { + return this.#inner.sources[sourceType]; + } } export class CodeGenerationResults { diff --git a/packages/rspack/src/config/adapterRuleUse.ts b/packages/rspack/src/config/adapterRuleUse.ts index 2a509a8c504..d891f80843f 100644 --- a/packages/rspack/src/config/adapterRuleUse.ts +++ b/packages/rspack/src/config/adapterRuleUse.ts @@ -140,6 +140,11 @@ export interface LoaderContext { getContextDependencies(): string[]; getMissingDependencies(): string[]; addBuildDependency(file: string): void; + importModule( + request: string, + options: { publicPath: string; baseUri: string }, + callback: (err?: Error, res?: any) => void + ): void; fs: any; utils: { absolutify: (context: string, request: string) => string; diff --git a/packages/rspack/src/loader-runner/index.ts b/packages/rspack/src/loader-runner/index.ts index a7354585554..cf4b7fb8559 100644 --- a/packages/rspack/src/loader-runner/index.ts +++ b/packages/rspack/src/loader-runner/index.ts @@ -239,6 +239,80 @@ export async function runLoaders( missingDependencies.length = 0; cacheable = true; }; + loaderContext.importModule = function importModule( + request, + options, + callback + ) { + const executeModuleKey = request + options.publicPath + options.baseUri; + if (!callback) { + return new Promise((resolve, reject) => { + compiler.compilation + .__internal_getInner() + .importModule( + request, + options.publicPath, + options.baseUri, + rawContext._moduleIdentifier, + loaderContext.context, + (err, res) => { + if (err) reject(err); + else { + for (const dep of res.buildDependencies) { + this.addBuildDependency(dep); + } + for (const dep of res.contextDependencies) { + this.addContextDependency(dep); + } + for (const dep of res.missingDependencies) { + this.addMissingDependency(dep); + } + for (const dep of res.fileDependencies) { + this.addDependency(dep); + } + assetFilenames.push(...res.assets); + + resolve(compiler.__internal__getModuleExecutionResult(res.id)); + } + } + ); + }); + } + return compiler.compilation + .__internal_getInner() + .importModule( + request, + options.publicPath, + options.baseUri, + rawContext._moduleIdentifier, + loaderContext.context, + (err, res) => { + if (err) { + callback(err, undefined); + } else { + for (const dep of res.buildDependencies) { + this.addBuildDependency(dep); + } + for (const dep of res.contextDependencies) { + this.addContextDependency(dep); + } + for (const dep of res.missingDependencies) { + this.addMissingDependency(dep); + } + for (const dep of res.fileDependencies) { + this.addDependency(dep); + } + assetFilenames.push(...res.assets); + + callback( + undefined, + compiler.__internal__getModuleExecutionResult(res.id) + ); + } + } + ); + }; + Object.defineProperty(loaderContext, "resource", { enumerable: true, get: function () { diff --git a/packages/rspack/tests/configCases/loader-import-module/css/colors.js b/packages/rspack/tests/configCases/loader-import-module/css/colors.js new file mode 100644 index 00000000000..d3da74ec7a1 --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/colors.js @@ -0,0 +1,2 @@ +export const red = "#f00"; +export const green = "#0f0"; diff --git a/packages/rspack/tests/configCases/loader-import-module/css/file.jpg b/packages/rspack/tests/configCases/loader-import-module/css/file.jpg new file mode 100644 index 00000000000..fe5c6eefa79 Binary files /dev/null and b/packages/rspack/tests/configCases/loader-import-module/css/file.jpg differ diff --git a/packages/rspack/tests/configCases/loader-import-module/css/file.png b/packages/rspack/tests/configCases/loader-import-module/css/file.png new file mode 100644 index 00000000000..fb53b9dedd3 Binary files /dev/null and b/packages/rspack/tests/configCases/loader-import-module/css/file.png differ diff --git a/packages/rspack/tests/configCases/loader-import-module/css/index.js b/packages/rspack/tests/configCases/loader-import-module/css/index.js new file mode 100644 index 00000000000..e512ec19cba --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/index.js @@ -0,0 +1,11 @@ +import stylesheet from "./stylesheet"; +import otherStylesheet from "./other-stylesheet"; + +it("should be able to use build-time code", () => { + expect(stylesheet).toBe( + 'body { background: url("my-schema://base/public/assets/file.png"); color: #f00; }' + ); + expect(otherStylesheet).toBe( + 'body { background: url("/other/assets/file.jpg"); color: #0f0; }' + ); +}); diff --git a/packages/rspack/tests/configCases/loader-import-module/css/loader.js b/packages/rspack/tests/configCases/loader-import-module/css/loader.js new file mode 100644 index 00000000000..118f7431844 --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/loader.js @@ -0,0 +1,13 @@ +/** @type {import("../../../../").PitchLoaderDefinitionFunction} */ +exports.pitch = async function (remaining) { + try { + const result = await this.importModule( + this.resourcePath + ".webpack[javascript/auto]" + "!=!" + remaining, + this.getOptions() + ); + return result.default || result; + } catch (e) { + console.error(e); + throw e; + } +}; diff --git a/packages/rspack/tests/configCases/loader-import-module/css/other-stylesheet.js b/packages/rspack/tests/configCases/loader-import-module/css/other-stylesheet.js new file mode 100644 index 00000000000..d3be25b7f28 --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/other-stylesheet.js @@ -0,0 +1,3 @@ +import { green } from "./colors.js"; +import file from "./file.jpg"; +export default `body { background: url("${file}"); color: ${green}; }`; diff --git a/packages/rspack/tests/configCases/loader-import-module/css/stylesheet.js b/packages/rspack/tests/configCases/loader-import-module/css/stylesheet.js new file mode 100644 index 00000000000..30116d4f4fb --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/stylesheet.js @@ -0,0 +1,4 @@ +import { red } from "./colors.js"; +export default `body { background: url("${ + new URL("./file.png", import.meta.url).href +}"); color: ${red}; }`; diff --git a/packages/rspack/tests/configCases/loader-import-module/css/webpack.config.js b/packages/rspack/tests/configCases/loader-import-module/css/webpack.config.js new file mode 100644 index 00000000000..2c03d3fc7a4 --- /dev/null +++ b/packages/rspack/tests/configCases/loader-import-module/css/webpack.config.js @@ -0,0 +1,77 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + output: { + publicPath: "/public/" + }, + entry: "./index.js", + module: { + parser: { + javascript: { + // url: "relative" + } + }, + rules: [ + { + dependency: "url", + issuer: /stylesheet\.js$/, + type: "asset/resource", + generator: { + filename: "assets/[name][ext][query]" + } + }, + { + oneOf: [ + { + test: /other-stylesheet\.js$/, + loader: "./loader", + options: { + publicPath: "/other/", + baseUri: "my-schema://base" + }, + type: "asset/source" + }, + { + test: /stylesheet\.js$/, + loader: "./loader", + options: { + baseUri: "my-schema://base" + }, + type: "asset/source" + } + ] + }, + { + test: /\.jpg$/, + type: "asset/resource", + generator: { + filename: "assets/[name][ext]" + } + } + ] + }, + plugins: [ + compiler => + compiler.hooks.done.tap("test case", stats => { + try { + expect(stats.compilation.getAsset("assets/file.png")).toHaveProperty( + "info", + expect.objectContaining({ sourceFilename: "file.png" }) + ); + expect(stats.compilation.getAsset("assets/file.jpg")).toHaveProperty( + "info", + expect.objectContaining({ sourceFilename: "file.jpg" }) + ); + const { auxiliaryFiles } = stats.compilation.namedChunks.get("main"); + expect(auxiliaryFiles).toContain("assets/file.png"); + } catch (e) { + console.log(stats.toString({ colors: true, orphanModules: true })); + throw e; + } + }) + ], + experiments: { + rspackFuture: { + newTreeshaking: true + } + } +}; diff --git a/webpack-test/ConfigTestCases.template.js b/webpack-test/ConfigTestCases.template.js index 2b05905a353..4484a8275f6 100644 --- a/webpack-test/ConfigTestCases.template.js +++ b/webpack-test/ConfigTestCases.template.js @@ -21,15 +21,16 @@ const filterInfraStructureErrors = require("./helpers/infrastructureLogErrors"); const { normalizeFilteredTestName } = require('./lib/util/filterUtil') const casesPath = path.join(__dirname, "configCases"); -const categories = fs.readdirSync(casesPath).map(cat => { - return { - name: cat, - tests: fs - .readdirSync(path.join(casesPath, cat)) - .filter(folder => !folder.startsWith("_")) - .sort() - }; -}); +const categories = fs.readdirSync(casesPath) + .map(cat => { + return { + name: cat, + tests: fs + .readdirSync(path.join(casesPath, cat)) + .filter(folder => !folder.startsWith("_")) + .sort() + }; + }); const createLogger = appendTarget => { return { @@ -39,14 +40,14 @@ const createLogger = appendTarget => { info: l => appendTarget.push(l), warn: console.warn.bind(console), error: console.error.bind(console), - logTime: () => {}, - group: () => {}, - groupCollapsed: () => {}, - groupEnd: () => {}, - profile: () => {}, - profileEnd: () => {}, - clear: () => {}, - status: () => {} + logTime: () => { }, + group: () => { }, + groupCollapsed: () => { }, + groupEnd: () => { }, + profile: () => { }, + profileEnd: () => { }, + clear: () => { }, + status: () => { } }; }; @@ -78,7 +79,7 @@ const describeCases = config => { if (flag !== true) { let filteredName = normalizeFilteredTestName(flag, testName); describe.skip(testName, () => { - it(filteredName, () => {}); + it(filteredName, () => { }); }); return; } @@ -152,7 +153,7 @@ const describeCases = config => { // } }); testConfig = { - findBundle: function(i, options) { + findBundle: function (i, options) { const ext = path.extname( parseResource(options.output.filename).path ); @@ -274,27 +275,27 @@ const describeCases = config => { ) ); } -// const allModules = children -// ? children.reduce( -// (all, { modules }) => all.concat(modules), -// modules || [] -// ) -// : modules; -// if ( -// allModules.some( -// m => m.type !== "cached modules" && !m.cached -// ) -// ) { -// return done( -// new Error( -// `Some modules were not cached:\n${stats.toString({ -// all: false, -// modules: true, -// modulesSpace: 100 -// })}` -// ) -// ); -// } + // const allModules = children + // ? children.reduce( + // (all, { modules }) => all.concat(modules), + // modules || [] + // ) + // : modules; + // if ( + // allModules.some( + // m => m.type !== "cached modules" && !m.cached + // ) + // ) { + // return done( + // new Error( + // `Some modules were not cached:\n${stats.toString({ + // all: false, + // modules: true, + // modulesSpace: 100 + // })}` + // ) + // ); + // } } const infrastructureLogErrors = filterInfraStructureErrors( infraStructureLog, diff --git a/webpack-test/configCases/loader-import-module/css/test.filter.js b/webpack-test/configCases/loader-import-module/css/test.filter.js index 3be456dcd23..02971e02798 100644 --- a/webpack-test/configCases/loader-import-module/css/test.filter.js +++ b/webpack-test/configCases/loader-import-module/css/test.filter.js @@ -1 +1,8 @@ -module.exports = () => {return false} \ No newline at end of file +const { FilteredStatus } = require("../../../lib/util/filterUtil") + +module.exports = () => { + return [ + FilteredStatus.PARTIAL_PASS, + "https://github.com/web-infra-dev/rspack/issues/4923" + ] +}