From 85223406c3d07e99eaeaebf4d32f089adc8d8525 Mon Sep 17 00:00:00 2001 From: jserfeng <1114550440@qq.com> Date: Tue, 5 Mar 2024 17:44:04 +0800 Subject: [PATCH] feat: lazy compilation --- Cargo.lock | 18 + crates/node_binding/binding.d.ts | 22 +- crates/rspack_binding_options/Cargo.toml | 1 + .../src/options/raw_builtins/mod.rs | 24 +- .../raw_builtins/raw_lazy_compilation.rs | 70 ++ .../src/dependency/dependency_type.rs | 2 + crates/rspack_core/src/module_factory.rs | 2 +- .../rspack_core/src/normal_module_factory.rs | 8 +- crates/rspack_core/src/plugin/args.rs | 6 +- crates/rspack_core/src/utils/queue.rs | 8 + crates/rspack_database/src/database.rs | 8 + crates/rspack_macros/src/hook.rs | 75 +- crates/rspack_macros/src/lib.rs | 2 +- crates/rspack_plugin_javascript/Cargo.toml | 1 + .../rspack_plugin_lazy_compilation/Cargo.toml | 19 + .../rspack_plugin_lazy_compilation/expand.rs | 957 ++++++++++++++++++ .../src/backend.rs | 14 + .../src/dependency.rs | 87 ++ .../src/factory.rs | 50 + .../rspack_plugin_lazy_compilation/src/lib.rs | 6 + .../src/module.rs | 323 ++++++ .../src/plugin.rs | 162 +++ .../src/lazy_compilation.rs | 153 --- crates/rspack_plugin_runtime/src/lib.rs | 2 - packages/rspack-dev-server/src/server.ts | 28 - packages/rspack/src/Compiler.ts | 1 + packages/rspack/src/Watching.ts | 4 + packages/rspack/src/builtin-plugin/index.ts | 1 + .../lazy-compilation/backend.ts | 229 +++++ .../lazy-compilation/lazyCompilation.ts | 18 + .../builtin-plugin/lazy-compilation/plugin.ts | 68 ++ packages/rspack/src/config/normalization.ts | 11 +- packages/rspack/src/config/zod.ts | 13 +- packages/rspack/src/rspackOptionsApply.ts | 21 +- .../lazy-compilation/context/test.filter.js | 3 - .../context/webpack.config.js | 1 + .../lazy-compilation/https/test.filter.js | 3 +- .../lazy-compilation/https/webpack.config.js | 1 + .../module-test/test.filter.js | 3 - .../module-test/webpack.config.js | 3 +- .../only-entries/test.filter.js | 3 - .../lazy-compilation/simple/test.filter.js | 3 +- .../lazy-compilation/simple/webpack.config.js | 3 +- .../lazy-compilation/unrelated/test.filter.js | 3 - 44 files changed, 2204 insertions(+), 236 deletions(-) create mode 100644 crates/rspack_binding_options/src/options/raw_builtins/raw_lazy_compilation.rs create mode 100644 crates/rspack_plugin_lazy_compilation/Cargo.toml create mode 100644 crates/rspack_plugin_lazy_compilation/expand.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/backend.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/dependency.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/factory.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/lib.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/module.rs create mode 100644 crates/rspack_plugin_lazy_compilation/src/plugin.rs delete mode 100644 crates/rspack_plugin_runtime/src/lazy_compilation.rs create mode 100644 packages/rspack/src/builtin-plugin/lazy-compilation/backend.ts create mode 100644 packages/rspack/src/builtin-plugin/lazy-compilation/lazyCompilation.ts create mode 100644 packages/rspack/src/builtin-plugin/lazy-compilation/plugin.ts delete mode 100644 webpack-test/hotCases/lazy-compilation/context/test.filter.js delete mode 100644 webpack-test/hotCases/lazy-compilation/module-test/test.filter.js delete mode 100644 webpack-test/hotCases/lazy-compilation/only-entries/test.filter.js delete mode 100644 webpack-test/hotCases/lazy-compilation/unrelated/test.filter.js diff --git a/Cargo.lock b/Cargo.lock index a059ecef6be..f2ae308b2b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3049,6 +3049,7 @@ dependencies = [ "rspack_plugin_html", "rspack_plugin_javascript", "rspack_plugin_json", + "rspack_plugin_lazy_compilation", "rspack_plugin_library", "rspack_plugin_limit_chunk_count", "rspack_plugin_merge_duplicate_chunks", @@ -3558,6 +3559,23 @@ dependencies = [ "rspack_testing", ] +[[package]] +name = "rspack_plugin_lazy_compilation" +version = "0.1.0" +dependencies = [ + "async-trait", + "once_cell", + "rspack_core", + "rspack_error", + "rspack_hook", + "rspack_identifier", + "rspack_plugin_javascript", + "rspack_regex", + "rspack_util", + "rustc-hash", + "tokio", +] + [[package]] name = "rspack_plugin_library" version = "0.1.0" diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index eae4dd7a933..1d718d70b90 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -174,7 +174,8 @@ export enum BuiltinPluginName { SwcJsMinimizerRspackPlugin = 'SwcJsMinimizerRspackPlugin', SwcCssMinimizerRspackPlugin = 'SwcCssMinimizerRspackPlugin', BundlerInfoRspackPlugin = 'BundlerInfoRspackPlugin', - JsLoaderRspackPlugin = 'JsLoaderRspackPlugin' + JsLoaderRspackPlugin = 'JsLoaderRspackPlugin', + LazyCompilation = 'LazyCompilation' } export function cleanupGlobalTrace(): void @@ -907,6 +908,14 @@ export interface RawJavascriptParserOptions { url: string } +export interface RawLazyCompilationOption { + module: (err: Error | null, arg: RawModuleArg) => any + test?: RawRegexMatcher + entries: boolean + imports: boolean + cacheable: boolean +} + export interface RawLibraryAuxiliaryComment { root?: string commonjs?: string @@ -942,6 +951,11 @@ export interface RawLimitChunkCountPluginOptions { maxChunks: number } +export interface RawModuleArg { + module: string + path: string +} + export interface RawModuleFilenameTemplateFnCtx { identifier: string shortIdentifier: string @@ -956,6 +970,12 @@ export interface RawModuleFilenameTemplateFnCtx { namespace: string } +export interface RawModuleInfo { + active: boolean + client: string + data: string +} + export interface RawModuleOptions { rules: Array parser?: Record diff --git a/crates/rspack_binding_options/Cargo.toml b/crates/rspack_binding_options/Cargo.toml index 0df870bae29..b6d83c1af5b 100644 --- a/crates/rspack_binding_options/Cargo.toml +++ b/crates/rspack_binding_options/Cargo.toml @@ -37,6 +37,7 @@ rspack_plugin_hmr = { path = "../rspack_plugin_hmr" } rspack_plugin_html = { path = "../rspack_plugin_html" } rspack_plugin_javascript = { path = "../rspack_plugin_javascript" } rspack_plugin_json = { path = "../rspack_plugin_json" } +rspack_plugin_lazy_compilation = { path = "../rspack_plugin_lazy_compilation" } rspack_plugin_library = { path = "../rspack_plugin_library" } rspack_plugin_limit_chunk_count = { path = "../rspack_plugin_limit_chunk_count" } rspack_plugin_merge_duplicate_chunks = { path = "../rspack_plugin_merge_duplicate_chunks" } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs index 08df52e3f34..c1ae0bd9419 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -2,6 +2,7 @@ mod raw_banner; mod raw_bundle_info; mod raw_copy; mod raw_html; +mod raw_lazy_compilation; mod raw_limit_chunk_count; mod raw_mf; mod raw_progress; @@ -10,7 +11,7 @@ mod raw_to_be_deprecated; use napi::{bindgen_prelude::FromNapiValue, JsUnknown}; use napi_derive::napi; -use rspack_core::{BoxPlugin, Define, DefinePlugin, PluginExt, Provide, ProvidePlugin}; +use rspack_core::{BoxPlugin, Define, DefinePlugin, Plugin, PluginExt, Provide, ProvidePlugin}; use rspack_error::Result; use rspack_ids::{ DeterministicChunkIdsPlugin, DeterministicModuleIdsPlugin, NamedChunkIdsPlugin, @@ -58,6 +59,7 @@ use rspack_plugin_warn_sensitive_module::WarnCaseSensitiveModulesPlugin; use rspack_plugin_wasm::{enable_wasm_loading_plugin, AsyncWasmPlugin}; use rspack_plugin_web_worker_template::web_worker_template_plugin; use rspack_plugin_worker::WorkerPlugin; +use rspack_regex::RspackRegex; pub use self::{ raw_banner::RawBannerPluginOptions, raw_copy::RawCopyRspackPluginOptions, @@ -67,6 +69,7 @@ pub use self::{ }; use self::{ raw_bundle_info::{RawBundlerInfoModeWrapper, RawBundlerInfoPluginOptions}, + raw_lazy_compilation::{JsBackend, RawLazyCompilationOption}, raw_mf::{RawConsumeSharedPluginOptions, RawContainerReferencePluginOptions, RawProvideOptions}, }; use crate::{ @@ -142,6 +145,7 @@ pub enum BuiltinPluginName { // rspack js adapter plugins // naming format follow XxxRspackPlugin JsLoaderRspackPlugin, + LazyCompilation, } #[napi(object)] @@ -403,6 +407,24 @@ impl BuiltinPlugin { JsLoaderResolverPlugin::new(downcast_into::(self.options)?).boxed(), ); } + BuiltinPluginName::LazyCompilation => { + let options = downcast_into::(self.options)?; + let js_backend = JsBackend::try_from(&options).expect("failed to convert function"); + plugins.push(Box::new( + rspack_plugin_lazy_compilation::plugin::LazyCompilationPlugin::new( + options.cacheable, + js_backend, + options.test.map(|s| { + RspackRegex::with_flags(&s.source, &s.flags).unwrap_or_else(|_| { + let msg = format!("[lazyCompilation]incorrect regex {:?}", s); + panic!("{msg}"); + }) + }), + options.entries, + options.imports, + ), + ) as Box) + } } Ok(()) } diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_lazy_compilation.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_lazy_compilation.rs new file mode 100644 index 00000000000..9f1f64a9a4a --- /dev/null +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_lazy_compilation.rs @@ -0,0 +1,70 @@ +use napi_derive::napi; +use rspack_core::ModuleIdentifier; +use rspack_napi::threadsafe_function::ThreadsafeFunction; +use rspack_plugin_lazy_compilation::backend::{Backend, ModuleInfo}; + +use crate::RawRegexMatcher; + +#[napi(object)] +pub struct RawModuleInfo { + pub active: bool, + pub client: String, + pub data: String, +} + +#[napi(object, object_to_js = false)] +pub struct RawLazyCompilationOption { + pub module: ThreadsafeFunction, + pub test: Option, + pub entries: bool, + pub imports: bool, + pub cacheable: bool, +} + +#[napi(object)] +pub struct RawModuleArg { + pub module: String, + pub path: String, +} + +pub(crate) struct JsBackend { + module: ThreadsafeFunction, +} + +impl std::fmt::Debug for JsBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("JsBackend").finish() + } +} + +impl From<&RawLazyCompilationOption> for JsBackend { + fn from(value: &RawLazyCompilationOption) -> Self { + Self { + module: value.module.clone(), + } + } +} + +#[async_trait::async_trait] +impl Backend for JsBackend { + async fn module( + &mut self, + identifier: ModuleIdentifier, + path: String, + ) -> rspack_error::Result { + let module_info = self + .module + .call(RawModuleArg { + module: identifier.to_string(), + path, + }) + .await + .expect("channel should have result"); + + Ok(ModuleInfo { + active: module_info.active, + client: module_info.client, + data: module_info.data, + }) + } +} diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index 4008173f22f..da0b6da8d08 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -93,6 +93,7 @@ pub enum DependencyType { /// Webpack is included WebpackIsIncluded, LoaderImport, + LazyImport, Custom(Box), // TODO it will increase large layout size } @@ -149,6 +150,7 @@ impl DependencyType { DependencyType::ProvideModuleForShared => Cow::Borrowed("provide module for shared"), DependencyType::ConsumeSharedFallback => Cow::Borrowed("consume shared fallback"), DependencyType::WebpackIsIncluded => Cow::Borrowed("__webpack_is_included__"), + DependencyType::LazyImport => Cow::Borrowed("lazy import()"), } } } diff --git a/crates/rspack_core/src/module_factory.rs b/crates/rspack_core/src/module_factory.rs index a656b8f4ae3..b7118f963dc 100644 --- a/crates/rspack_core/src/module_factory.rs +++ b/crates/rspack_core/src/module_factory.rs @@ -6,7 +6,7 @@ use sugar_path::SugarPathBuf; use crate::{BoxDependency, BoxModule, Context, FactoryMeta, ModuleIdentifier, Resolve}; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ModuleFactoryCreateData { pub resolve_options: Option>, pub context: Context, diff --git a/crates/rspack_core/src/normal_module_factory.rs b/crates/rspack_core/src/normal_module_factory.rs index 03c4d7579c2..2157518c19d 100644 --- a/crates/rspack_core/src/normal_module_factory.rs +++ b/crates/rspack_core/src/normal_module_factory.rs @@ -6,7 +6,7 @@ use rspack_error::{error, Result}; use rspack_hook::AsyncSeriesBailHook; use rspack_loader_runner::{get_scheme, Loader, Scheme}; use sugar_path::{AsPath, SugarPath}; -use swc_core::common::Span; +use swc_core::common::{util::take::Take, Span}; use crate::{ cache::Cache, @@ -580,13 +580,16 @@ impl NormalModuleFactory { .unwrap_or_else(|| unreachable!()) }; + let mut diagnostics = data.diagnostics.take(); let mut create_data = NormalModuleCreateData { dependency_type: data.dependency.dependency_type().clone(), resolve_data_request: dependency.request(), resource_resolve_data: after_resolve_create_data.resource.clone(), context: data.context.clone(), - diagnostics: &mut data.diagnostics, + diagnostics: &mut diagnostics, + create_data: data, }; + let module = if let Some(module) = self .plugin_driver .normal_module_factory_create_module(&mut create_data) @@ -615,6 +618,7 @@ impl NormalModuleFactory { .plugin_driver .normal_module_factory_module(module, &mut create_data) .await?; + data.diagnostics = diagnostics; data.add_file_dependencies(file_dependencies); data.add_file_dependency(file_dependency); diff --git a/crates/rspack_core/src/plugin/args.rs b/crates/rspack_core/src/plugin/args.rs index 19aa5439298..6262e074649 100644 --- a/crates/rspack_core/src/plugin/args.rs +++ b/crates/rspack_core/src/plugin/args.rs @@ -10,8 +10,9 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ BoxModule, Chunk, ChunkInitFragments, ChunkUkey, Compilation, Context, ContextModuleFactory, - DependencyCategory, DependencyType, ErrorSpan, FactoryMeta, ModuleDependency, ModuleIdentifier, - NormalModuleFactory, Resolve, RuntimeGlobals, SharedPluginDriver, Stats, + DependencyCategory, DependencyType, ErrorSpan, FactoryMeta, ModuleDependency, + ModuleFactoryCreateData, ModuleIdentifier, NormalModuleFactory, Resolve, RuntimeGlobals, + SharedPluginDriver, Stats, }; #[derive(Debug)] @@ -80,6 +81,7 @@ pub struct NormalModuleCreateData<'a> { pub resource_resolve_data: ResourceData, pub context: Context, pub diagnostics: &'a mut Vec, + pub create_data: &'a ModuleFactoryCreateData, } #[derive(Debug)] diff --git a/crates/rspack_core/src/utils/queue.rs b/crates/rspack_core/src/utils/queue.rs index f337e5789e2..70d1c135479 100644 --- a/crates/rspack_core/src/utils/queue.rs +++ b/crates/rspack_core/src/utils/queue.rs @@ -32,6 +32,14 @@ impl WorkerQueue { } } + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + pub fn add_task(&mut self, task: T) -> usize { self.inner.push_back(task); self.inner.len() diff --git a/crates/rspack_database/src/database.rs b/crates/rspack_database/src/database.rs index afd6283f65b..9bf7f6d4d01 100644 --- a/crates/rspack_database/src/database.rs +++ b/crates/rspack_database/src/database.rs @@ -31,6 +31,14 @@ impl Database { } } + pub fn len(&self) -> usize { + self.inner.len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + pub fn contains(&self, id: &Ukey) -> bool { self.inner.contains_key(id) } diff --git a/crates/rspack_macros/src/hook.rs b/crates/rspack_macros/src/hook.rs index 116b9c4c1ba..1409fc3ff37 100644 --- a/crates/rspack_macros/src/hook.rs +++ b/crates/rspack_macros/src/hook.rs @@ -1,13 +1,16 @@ -use quote::quote; +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream, Parser}, - Result, Token, + Generics, Result, Token, }; pub fn expand_struct(mut input: syn::ItemStruct) -> proc_macro::TokenStream { let ident = &input.ident; + let generics = &input.generics; let inner_ident = plugin_inner_ident(ident); + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let inner_fields = input.fields.clone(); let is_named_struct = matches!(&inner_fields, syn::Fields::Named(_)); let is_unit_struct = matches!(&inner_fields, syn::Fields::Unit); @@ -19,7 +22,7 @@ pub fn expand_struct(mut input: syn::ItemStruct) -> proc_macro::TokenStream { input.fields = syn::Fields::Named( syn::FieldsNamed::parse - .parse2(quote! { { inner: ::std::sync::Arc<#inner_ident> } }) + .parse2(quote! { { inner: ::std::sync::Arc<#inner_ident #ty_generics> } }) .expect("Failed to parse"), ); @@ -41,7 +44,7 @@ pub fn expand_struct(mut input: syn::ItemStruct) -> proc_macro::TokenStream { quote! { fn new_inner() -> Self { Self { - inner: ::std::sync::Arc::new(#inner_ident), + inner: ::std::sync::Arc::new(#inner_ident #ty_generics), } } } @@ -51,33 +54,33 @@ pub fn expand_struct(mut input: syn::ItemStruct) -> proc_macro::TokenStream { let inner_struct = if is_named_struct { quote! { - pub struct #inner_ident #inner_fields + pub struct #inner_ident #impl_generics #where_clause #inner_fields } } else { quote! { - pub struct #inner_ident; + pub struct #inner_ident #impl_generics #where_clause ; } }; let expanded = quote! { #input - impl #ident { + impl #impl_generics #ident #ty_generics #where_clause { #new_inner_fn - fn from_inner(inner: &::std::sync::Arc<#inner_ident>) -> Self { + fn from_inner(inner: &::std::sync::Arc<#inner_ident #ty_generics>) -> Self { Self { inner: ::std::sync::Arc::clone(inner), } } - fn inner(&self) -> &::std::sync::Arc<#inner_ident> { + fn inner(&self) -> &::std::sync::Arc<#inner_ident #ty_generics> { &self.inner } } - impl ::std::ops::Deref for #ident { - type Target = #inner_ident; + impl #impl_generics ::std::ops::Deref for #ident #ty_generics #where_clause { + type Target = #inner_ident #ty_generics; fn deref(&self) -> &Self::Target { &self.inner } @@ -90,15 +93,25 @@ pub fn expand_struct(mut input: syn::ItemStruct) -> proc_macro::TokenStream { expanded.into() } -fn plugin_inner_ident(ident: &syn::Ident) -> syn::Ident { +fn plugin_inner_ident(ident: &syn::Ident) -> impl ToTokens { let inner_name = format!("{}Inner", ident); - syn::Ident::new(&inner_name, ident.span()) + let ident = syn::Ident::new(&inner_name, ident.span()); + + // let (_impl_generics, type_generics, _where_clause) = + // if let Some(generics) = generics.map(|generics| generics.split_for_impl()) { + // (Some(generics.0), Some(generics.1), Some(generics.2)) + // } else { + // (None, None, None) + // }; + + ident } pub struct HookArgs { trait_: syn::Path, name: syn::Ident, stage: Option, + generics: Option, } impl Parse for HookArgs { @@ -118,10 +131,19 @@ impl Parse for HookArgs { _ => return Err(input.error("expected \"stage\" or end of attribute")), } } + + let generics = if input.peek(Token![<]) { + let generics = input.parse::()?; + Some(generics) + } else { + None + }; + Ok(Self { trait_, name, stage, + generics, }) } } @@ -131,10 +153,12 @@ pub fn expand_fn(args: HookArgs, input: syn::ItemFn) -> proc_macro::TokenStream name, trait_, stage, + generics, } = args; let syn::ItemFn { mut sig, block, .. } = input; + let real_sig = sig.clone(); - let mut rest_args = Vec::new(); + let mut rest_args: Vec<&Box> = Vec::new(); for arg in real_sig.inputs.iter().skip(1) { if let syn::FnArg::Typed(syn::PatType { pat, .. }) = arg { rest_args.push(pat) @@ -170,34 +194,41 @@ pub fn expand_fn(args: HookArgs, input: syn::ItemFn) -> proc_macro::TokenStream quote! { #name::#fn_ident(&#name::from_inner(&self.inner), #(#rest_args,)*) } }; + let (impl_generics, type_generics, where_clause) = if let Some(generics) = &generics { + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + (Some(impl_generics), Some(type_generics), where_clause) + } else { + (None, None, None) + }; + let expanded = quote! { #[allow(non_camel_case_types)] - struct #fn_ident { - inner: ::std::sync::Arc<#inner_ident>, + struct #fn_ident #impl_generics #where_clause { + inner: ::std::sync::Arc<#inner_ident #type_generics>, } - impl #fn_ident { - pub(crate) fn new(plugin: &#name) -> Box { + impl #impl_generics #fn_ident #type_generics #where_clause { + pub(crate) fn new(plugin: &#name #type_generics) -> Box { Box::new(#fn_ident { inner: ::std::sync::Arc::clone(plugin.inner()), }) } } - impl #name { + impl #impl_generics #name #type_generics #where_clause { #[allow(clippy::ptr_arg)] #real_sig #block } - impl ::std::ops::Deref for #fn_ident { - type Target = #inner_ident; + impl #impl_generics ::std::ops::Deref for #fn_ident #type_generics #where_clause { + type Target = #inner_ident #type_generics; fn deref(&self) -> &Self::Target { &self.inner } } #attr - impl #trait_ for #fn_ident { + impl #impl_generics #trait_ for #fn_ident #type_generics #where_clause { #sig { #call_real_fn } diff --git a/crates/rspack_macros/src/lib.rs b/crates/rspack_macros/src/lib.rs index 6960bca330a..be240189e66 100644 --- a/crates/rspack_macros/src/lib.rs +++ b/crates/rspack_macros/src/lib.rs @@ -33,6 +33,6 @@ pub fn plugin_hook( tokens: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let args = syn::parse_macro_input!(args as hook::HookArgs); - let input = syn::parse_macro_input!(tokens as syn::ItemFn); + let input: syn::ItemFn = syn::parse_macro_input!(tokens as syn::ItemFn); hook::expand_fn(args, input) } diff --git a/crates/rspack_plugin_javascript/Cargo.toml b/crates/rspack_plugin_javascript/Cargo.toml index 884eae3503e..afb61262eb0 100644 --- a/crates/rspack_plugin_javascript/Cargo.toml +++ b/crates/rspack_plugin_javascript/Cargo.toml @@ -45,6 +45,7 @@ swc_core = { workspace = true, features = [ "ecma_transforms_proposal", "ecma_transforms_typescript", "ecma_quote", + "base", ] } swc_node_comments = { workspace = true } url = { workspace = true } diff --git a/crates/rspack_plugin_lazy_compilation/Cargo.toml b/crates/rspack_plugin_lazy_compilation/Cargo.toml new file mode 100644 index 00000000000..9a61fd5cd60 --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/Cargo.toml @@ -0,0 +1,19 @@ +[package] +edition = "2021" +license = "MIT" +name = "rspack_plugin_lazy_compilation" +version = "0.1.0" + +[dependencies] +async-trait = { workspace = true } +once_cell = { workspace = true } +rustc-hash = { workspace = true } +tokio = { workspace = true } + +rspack_core = { path = "../rspack_core" } +rspack_error = { path = "../rspack_error" } +rspack_hook = { path = "../rspack_hook" } +rspack_identifier = { path = "../rspack_identifier" } +rspack_plugin_javascript = { path = "../rspack_plugin_javascript" } +rspack_regex = { path = "../rspack_regex" } +rspack_util = { path = "../rspack_util" } diff --git a/crates/rspack_plugin_lazy_compilation/expand.rs b/crates/rspack_plugin_lazy_compilation/expand.rs new file mode 100644 index 00000000000..03b6edfbaae --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/expand.rs @@ -0,0 +1,957 @@ +#![feature(prelude_import)] +#![feature(let_chains)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +pub mod backend { + use rspack_core::ModuleIdentifier; + use rspack_error::Result; + pub struct ModuleInfo { + pub active: bool, + pub data: String, + pub client: String, + } + pub trait Backend: std::fmt::Debug + Send + Sync { + #[must_use] + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn module<'life0, 'async_trait>( + &'life0 mut self, + original_module: ModuleIdentifier, + path: String, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait; + } +} +mod dependency { + use std::path::PathBuf; + + use rspack_core::{ + AsContextDependency, AsDependencyTemplate, Context, Dependency, DependencyCategory, + DependencyId, DependencyType, ModuleDependency, NormalModuleCreateData, Resolve, ResourceData, + }; + use rspack_error::Diagnostic; + use rspack_identifier::Identifier; + use rustc_hash::FxHashSet as HashSet; + pub(crate) struct ProxyCreateData { + pub resolve_options: Option>, + pub resource_resolve_data: ResourceData, + pub context: Context, + pub issuer: Option>, + pub issuer_identifier: Option, + pub file_dependencies: HashSet, + pub context_dependencies: HashSet, + pub missing_dependencies: HashSet, + pub diagnostics: Vec, + } + #[automatically_derived] + impl ::core::fmt::Debug for ProxyCreateData { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + let names: &'static _ = &[ + "resolve_options", + "resource_resolve_data", + "context", + "issuer", + "issuer_identifier", + "file_dependencies", + "context_dependencies", + "missing_dependencies", + "diagnostics", + ]; + let values: &[&dyn ::core::fmt::Debug] = &[ + &self.resolve_options, + &self.resource_resolve_data, + &self.context, + &self.issuer, + &self.issuer_identifier, + &self.file_dependencies, + &self.context_dependencies, + &self.missing_dependencies, + &&self.diagnostics, + ]; + ::core::fmt::Formatter::debug_struct_fields_finish(f, "ProxyCreateData", names, values) + } + } + #[automatically_derived] + impl ::core::clone::Clone for ProxyCreateData { + #[inline] + fn clone(&self) -> ProxyCreateData { + ProxyCreateData { + resolve_options: ::core::clone::Clone::clone(&self.resolve_options), + resource_resolve_data: ::core::clone::Clone::clone(&self.resource_resolve_data), + context: ::core::clone::Clone::clone(&self.context), + issuer: ::core::clone::Clone::clone(&self.issuer), + issuer_identifier: ::core::clone::Clone::clone(&self.issuer_identifier), + file_dependencies: ::core::clone::Clone::clone(&self.file_dependencies), + context_dependencies: ::core::clone::Clone::clone(&self.context_dependencies), + missing_dependencies: ::core::clone::Clone::clone(&self.missing_dependencies), + diagnostics: ::core::clone::Clone::clone(&self.diagnostics), + } + } + } + impl ProxyCreateData { + pub(crate) fn new(module_create_data: &NormalModuleCreateData) -> Self { + Self { + resolve_options: module_create_data.create_data.resolve_options.clone(), + resource_resolve_data: module_create_data.resource_resolve_data.clone(), + context: module_create_data.context.clone(), + issuer: module_create_data.create_data.issuer.clone(), + issuer_identifier: module_create_data.create_data.issuer_identifier, + file_dependencies: module_create_data.create_data.file_dependencies.clone(), + context_dependencies: module_create_data.create_data.context_dependencies.clone(), + missing_dependencies: module_create_data.create_data.missing_dependencies.clone(), + diagnostics: module_create_data.diagnostics.clone(), + } + } + } + pub(crate) struct LazyCompilationDependency { + id: DependencyId, + pub original_module_create_data: ProxyCreateData, + request: String, + } + #[automatically_derived] + impl ::core::fmt::Debug for LazyCompilationDependency { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_struct_field3_finish( + f, + "LazyCompilationDependency", + "id", + &self.id, + "original_module_create_data", + &self.original_module_create_data, + "request", + &&self.request, + ) + } + } + #[automatically_derived] + impl ::core::clone::Clone for LazyCompilationDependency { + #[inline] + fn clone(&self) -> LazyCompilationDependency { + LazyCompilationDependency { + id: ::core::clone::Clone::clone(&self.id), + original_module_create_data: ::core::clone::Clone::clone(&self.original_module_create_data), + request: ::core::clone::Clone::clone(&self.request), + } + } + } + impl LazyCompilationDependency { + pub fn new(original_module_create_data: ProxyCreateData) -> Self { + let request = { + let res = ::alloc::fmt::format(format_args!( + "{0}?lazy-compilation-proxy-dep", + &original_module_create_data.resource_resolve_data.resource + )); + res + }; + Self { + id: DependencyId::new(), + original_module_create_data, + request, + } + } + } + impl ModuleDependency for LazyCompilationDependency { + fn request(&self) -> &str { + &self.request + } + } + impl AsDependencyTemplate for LazyCompilationDependency {} + impl AsContextDependency for LazyCompilationDependency {} + impl Dependency for LazyCompilationDependency { + fn dependency_debug_name(&self) -> &'static str { + "lazy compilation dependency" + } + fn id(&self) -> &rspack_core::DependencyId { + &self.id + } + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + fn dependency_type(&self) -> &DependencyType { + &DependencyType::LazyImport + } + } +} +mod factory { + use std::sync::Arc; + + use rspack_core::{ + ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, NormalModuleFactory, + }; + use rspack_error::Result; + + use crate::dependency::LazyCompilationDependency; + pub(crate) struct LazyCompilationDependencyFactory { + normal_module_factory: Arc, + } + #[automatically_derived] + impl ::core::fmt::Debug for LazyCompilationDependencyFactory { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_struct_field1_finish( + f, + "LazyCompilationDependencyFactory", + "normal_module_factory", + &&self.normal_module_factory, + ) + } + } + impl LazyCompilationDependencyFactory { + pub fn new(normal_module_factory: Arc) -> Self { + Self { + normal_module_factory, + } + } + } + impl ModuleFactory for LazyCompilationDependencyFactory { + #[allow( + clippy::async_yields_async, + clippy::diverging_sub_expression, + clippy::let_unit_value, + clippy::no_effect_underscore_binding, + clippy::shadow_same, + clippy::type_complexity, + clippy::type_repetition_in_bounds, + clippy::used_underscore_binding + )] + fn create<'life0, 'life1, 'async_trait>( + &'life0 self, + data: &'life1 mut ModuleFactoryCreateData, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + 'life1: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + if let ::core::option::Option::Some(__ret) = + ::core::option::Option::None::> + { + return __ret; + } + let __self = self; + let __ret: Result = { + let dep: &LazyCompilationDependency = data + .dependency + .as_any() + .downcast_ref() + .expect("should be lazy compile dependency"); + let proxy_data = &dep.original_module_create_data; + let dep = dep.clone(); + let mut create_data = ModuleFactoryCreateData { + resolve_options: proxy_data.resolve_options.clone(), + context: proxy_data.context.clone(), + dependency: Box::new(dep), + issuer: proxy_data.issuer.clone(), + issuer_identifier: proxy_data.issuer_identifier, + file_dependencies: proxy_data.file_dependencies.clone(), + context_dependencies: proxy_data.context_dependencies.clone(), + missing_dependencies: proxy_data.missing_dependencies.clone(), + diagnostics: proxy_data.diagnostics.clone(), + }; + __self.normal_module_factory.create(&mut create_data).await + }; + #[allow(unreachable_code)] + __ret + }) + } + } +} +mod module { + use std::{hash::Hash, path::PathBuf, sync::Arc}; + + use rspack_core::{ + impl_build_info_meta, module_namespace_promise, + rspack_sources::{RawSource, Source}, + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, + BuildInfo, BuildMeta, BuildResult, CodeGenerationResult, Compilation, ConcatenationScope, + Context, DependenciesBlock, DependencyId, Module, ModuleIdentifier, ModuleType, RuntimeGlobals, + RuntimeSpec, SourceType, TemplateContext, + }; + use rspack_error::{Diagnosable, Diagnostic, Result}; + use rspack_identifier::Identifiable; + use rspack_plugin_javascript::dependency::CommonJsRequireDependency; + use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind}; + use rustc_hash::FxHashSet; + + use crate::dependency::{LazyCompilationDependency, ProxyCreateData}; + static MODULE_TYPE: ModuleType = ModuleType::Js; + static SOURCE_TYPE: [SourceType; 1] = [SourceType::JavaScript]; + pub(crate) struct LazyCompilationProxyModule { + build_info: Option, + build_meta: Option, + original_module: ModuleIdentifier, + cacheable: bool, + readable_identifier: String, + identifier: ModuleIdentifier, + blocks: Vec, + dependencies: Vec, + source_map_kind: SourceMapKind, + create_data: ProxyCreateData, + pub request: String, + pub active: bool, + pub data: String, + pub client: String, + } + #[automatically_derived] + impl ::core::fmt::Debug for LazyCompilationProxyModule { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + let names: &'static _ = &[ + "build_info", + "build_meta", + "original_module", + "cacheable", + "readable_identifier", + "identifier", + "blocks", + "dependencies", + "source_map_kind", + "create_data", + "request", + "active", + "data", + "client", + ]; + let values: &[&dyn ::core::fmt::Debug] = &[ + &self.build_info, + &self.build_meta, + &self.original_module, + &self.cacheable, + &self.readable_identifier, + &self.identifier, + &self.blocks, + &self.dependencies, + &self.source_map_kind, + &self.create_data, + &self.request, + &self.active, + &self.data, + &&self.client, + ]; + ::core::fmt::Formatter::debug_struct_fields_finish( + f, + "LazyCompilationProxyModule", + names, + values, + ) + } + } + impl Hash for LazyCompilationProxyModule { + fn hash(&self, state: &mut H) { + self.build_meta.hash(state); + self.original_module.hash(state); + self.readable_identifier.hash(state); + self.identifier.hash(state); + self.blocks.hash(state); + self.dependencies.hash(state); + } + } + impl PartialEq for LazyCompilationProxyModule { + fn eq(&self, other: &Self) -> bool { + self.original_module == other.original_module + && self.readable_identifier == other.readable_identifier + && self.identifier == other.identifier + } + } + impl Eq for LazyCompilationProxyModule {} + impl ModuleSourceMapConfig for LazyCompilationProxyModule { + fn get_source_map_kind(&self) -> &SourceMapKind { + &self.source_map_kind + } + fn set_source_map_kind(&mut self, source_map: SourceMapKind) { + self.source_map_kind = source_map; + } + } + impl LazyCompilationProxyModule { + pub(crate) fn new( + original_module: ModuleIdentifier, + create_data: ProxyCreateData, + request: String, + cacheable: bool, + active: bool, + data: String, + client: String, + ) -> Self { + let readable_identifier = { + let res = ::alloc::fmt::format(format_args!( + "lazy-compilation-proxy|{0}", + create_data.context.shorten(&original_module) + )); + res + }; + let identifier = { + let res = ::alloc::fmt::format(format_args!("lazy-compilation-proxy|{0}", original_module)); + res + } + .into(); + Self { + build_info: None, + build_meta: None, + cacheable, + original_module, + create_data, + readable_identifier, + identifier, + source_map_kind: SourceMapKind::None, + blocks: ::alloc::vec::Vec::new(), + dependencies: ::alloc::vec::Vec::new(), + active, + request, + client, + data, + } + } + } + impl Diagnosable for LazyCompilationProxyModule { + fn add_diagnostic(&self, _diagnostic: Diagnostic) { + ::core::panicking::panic("not implemented") + } + fn add_diagnostics(&self, _diagnostics: Vec) { + ::core::panicking::panic("not implemented") + } + } + impl Module for LazyCompilationProxyModule { + fn build_info(&self) -> Option<&::rspack_core::BuildInfo> { + self.build_info.as_ref() + } + fn build_meta(&self) -> Option<&::rspack_core::BuildMeta> { + self.build_meta.as_ref() + } + fn set_module_build_info_and_meta( + &mut self, + build_info: ::rspack_core::BuildInfo, + build_meta: ::rspack_core::BuildMeta, + ) { + self.build_info = Some(build_info); + self.build_meta = Some(build_meta); + } + fn source_types(&self) -> &[SourceType] { + &SOURCE_TYPE + } + fn module_type(&self) -> &ModuleType { + &MODULE_TYPE + } + fn size(&self, _source_type: &SourceType) -> f64 { + 200f64 + } + fn original_source(&self) -> Option<&dyn Source> { + None + } + fn readable_identifier(&self, _context: &Context) -> std::borrow::Cow { + std::borrow::Cow::Borrowed(&self.readable_identifier) + } + fn get_diagnostics(&self) -> Vec { + ::alloc::vec::Vec::new() + } + #[allow( + clippy::async_yields_async, + clippy::diverging_sub_expression, + clippy::let_unit_value, + clippy::no_effect_underscore_binding, + clippy::shadow_same, + clippy::type_complexity, + clippy::type_repetition_in_bounds, + clippy::used_underscore_binding + )] + fn build<'life0, 'life1, 'life2, 'async_trait>( + &'life0 mut self, + _build_context: BuildContext<'life1>, + _compilation: Option<&'life2 Compilation>, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future> + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + 'life1: 'async_trait, + 'life2: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + if let ::core::option::Option::Some(__ret) = + ::core::option::Option::None::> + { + return __ret; + } + let mut __self = self; + let _build_context = _build_context; + let _compilation = _compilation; + let __ret: Result = { + let client_dep = CommonJsRequireDependency::new(__self.client.clone(), None, 0, 0, false); + let mut dependencies = ::alloc::vec::Vec::new(); + let mut blocks = ::alloc::vec::Vec::new(); + dependencies.push(Box::new(client_dep) as BoxDependency); + if __self.active { + let dep = LazyCompilationDependency::new(__self.create_data.clone()); + blocks.push(AsyncDependenciesBlock::new( + __self.identifier, + None, + None, + <[_]>::into_vec( + #[rustc_box] + ::alloc::boxed::Box::new([Box::new(dep)]), + ), + )); + } + let mut files = FxHashSet::default(); + files.extend(__self.create_data.file_dependencies.clone()); + files.insert(PathBuf::from( + &__self.create_data.resource_resolve_data.resource, + )); + Ok(BuildResult { + build_info: BuildInfo { + cacheable: __self.cacheable, + file_dependencies: files, + ..Default::default() + }, + build_meta: BuildMeta::default(), + analyze_result: Default::default(), + dependencies, + blocks, + optimization_bailouts: ::alloc::vec::Vec::new(), + }) + }; + #[allow(unreachable_code)] + __ret + }) + } + fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + mut concatenation_scope: Option, + ) -> Result { + let mut runtime_requirements = RuntimeGlobals::empty(); + runtime_requirements.insert(RuntimeGlobals::MODULE); + runtime_requirements.insert(RuntimeGlobals::REQUIRE); + let client_dep_id = self.dependencies[0]; + let module_graph = &compilation.get_module_graph(); + let chunk_graph = &compilation.chunk_graph; + let client_module = module_graph + .module_identifier_by_dependency_id(&client_dep_id) + .expect("should have module"); + let block = self.blocks.first(); + let client = { + let res = ::alloc::fmt::format(format_args!( + "var client = __webpack_require__(\"{0}\");\nvar data = \"{1}\"", + chunk_graph + .get_module_id(*client_module) + .as_ref() + .expect("should have module id"), + self.data + )); + res + }; + let keep_active = { + let res = ::alloc::fmt::format( + format_args!( + "var dispose = client.keepAlive({{ data: data, active: {0}, module: module, onError: onError }})", + block.is_some() + ), + ); + res + }; + let source = if let Some(block_id) = block { + let block = module_graph + .block_by_id(block_id) + .expect("should have block"); + let dep_id = block.get_dependencies()[0]; + let module = module_graph + .module_identifier_by_dependency_id(&dep_id) + .expect("should have module"); + let mut template_ctx = TemplateContext { + compilation, + module: module_graph + .module_by_identifier(module) + .expect("should have module") + .as_ref(), + runtime_requirements: &mut runtime_requirements, + init_fragments: &mut ::alloc::vec::Vec::new(), + runtime: None, + concatenation_scope: concatenation_scope.as_mut(), + }; + RawSource::from({ + let res = ::alloc::fmt::format( + format_args!( + "{3}\n module.exports = {0};\n if (module.hot) {{\n module.hot.accept();\n module.hot.accept(\"{1}\", function() {{ module.hot.invalidate(); }});\n module.hot.dispose(function(data) {{ delete data.resolveSelf; dispose(data); }});\n if (module.hot.data && module.hot.data.resolveSelf)\n module.hot.data.resolveSelf(module.exports);\n }}\n function onError() {{ /* ignore */ }}\n {2}\n ", + module_namespace_promise(& mut template_ctx, & dep_id, + Some(block_id), & self.request, "import()", false), + chunk_graph.get_module_id(* module).as_ref() + .expect("should have module id"), keep_active, client + ), + ); + res + }) + } else { + RawSource::from({ + let res = ::alloc::fmt::format( + format_args!( + "{0}\n var resolveSelf, onError;\n module.exports = new Promise(function(resolve, reject) {{ resolveSelf = resolve; onError = reject; }});\n if (module.hot) {{\n module.hot.accept();\n if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports);\n module.hot.dispose(function(data) {{ data.resolveSelf = resolveSelf; dispose(data); }});\n }}\n {1}\n ", + client, keep_active + ), + ); + res + }) + }; + let mut codegen_result = CodeGenerationResult::default().with_javascript(Arc::new(source)); + codegen_result.runtime_requirements = runtime_requirements; + codegen_result.set_hash( + &compilation.options.output.hash_function, + &compilation.options.output.hash_digest, + &compilation.options.output.hash_salt, + ); + Ok(codegen_result) + } + } + impl Identifiable for LazyCompilationProxyModule { + fn identifier(&self) -> rspack_identifier::Identifier { + self.identifier + } + } + impl DependenciesBlock for LazyCompilationProxyModule { + fn add_block_id(&mut self, block: rspack_core::AsyncDependenciesBlockIdentifier) { + self.blocks.push(block); + } + fn get_blocks(&self) -> &[rspack_core::AsyncDependenciesBlockIdentifier] { + &self.blocks + } + fn add_dependency_id(&mut self, dependency: rspack_core::DependencyId) { + self.dependencies.push(dependency); + } + fn get_dependencies(&self) -> &[rspack_core::DependencyId] { + &self.dependencies + } + } +} +pub mod plugin { + use std::sync::Arc; + + use once_cell::sync::Lazy; + use rspack_core::{ + BoxModule, Compilation, CompilationParams, DependencyType, ModuleFactory, + NormalModuleCreateData, Plugin, PluginContext, PluginNormalModuleFactoryModuleHookOutput, + }; + use rspack_hook::{plugin, plugin_hook, AsyncSeries2}; + use rspack_regex::RspackRegex; + use tokio::sync::Mutex; + + use crate::{ + backend::Backend, dependency::ProxyCreateData, factory::LazyCompilationDependencyFactory, + module::LazyCompilationProxyModule, + }; + static WEBPACK_DEV_SERVER_CLIENT_RE: Lazy = Lazy::new(|| { + RspackRegex::new( + r#"(webpack|rspack)[/\\]hot[/\\]|(webpack|rspack)-dev-server[/\\]client|(webpack|rspack)-hot-middleware[/\\]client"#, + ) + .expect("should compile regex") + }); + pub struct LazyCompilationPlugin { + inner: ::std::sync::Arc>, + } + #[automatically_derived] + impl ::core::fmt::Debug for LazyCompilationPlugin { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_struct_field1_finish( + f, + "LazyCompilationPlugin", + "inner", + &&self.inner, + ) + } + } + impl LazyCompilationPlugin { + #[allow(clippy::too_many_arguments)] + fn new_inner( + backend: Mutex, + entries: bool, + imports: bool, + test: Option, + cacheable: bool, + ) -> Self { + Self { + inner: ::std::sync::Arc::new(LazyCompilationPluginInner { + backend, + entries, + imports, + test, + cacheable, + }), + } + } + fn from_inner(inner: &::std::sync::Arc>) -> Self { + Self { + inner: ::std::sync::Arc::clone(inner), + } + } + fn inner(&self) -> &::std::sync::Arc> { + &self.inner + } + } + impl ::std::ops::Deref for LazyCompilationPlugin { + type Target = LazyCompilationPluginInner; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + #[doc(hidden)] + pub struct LazyCompilationPluginInner { + backend: Mutex, + entries: bool, + imports: bool, + test: Option, + cacheable: bool, + } + #[automatically_derived] + impl ::core::fmt::Debug for LazyCompilationPluginInner { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::fmt::Formatter::debug_struct_field5_finish( + f, + "LazyCompilationPluginInner", + "backend", + &self.backend, + "entries", + &self.entries, + "imports", + &self.imports, + "test", + &self.test, + "cacheable", + &&self.cacheable, + ) + } + } + impl LazyCompilationPlugin { + pub fn new( + cacheable: bool, + backend: T, + test: Option, + entries: bool, + imports: bool, + ) -> Self { + Self::new_inner(Mutex::new(backend), entries, imports, test, cacheable) + } + fn check_test(&self, module: &BoxModule) -> bool { + if let Some(test) = &self.test { + test.test(&module.name_for_condition().unwrap_or("".into())) + } else { + true + } + } + } + #[allow(non_camel_case_types)] + struct compilation { + inner: ::std::sync::Arc>, + } + impl compilation { + pub(crate) fn new(plugin: &LazyCompilationPlugin) -> Box { + Box::new(compilation { + inner: ::std::sync::Arc::clone(plugin.inner()), + }) + } + } + impl LazyCompilationPlugin { + #[allow(clippy::ptr_arg)] + async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, + ) -> Result<()> { + Ok(()) + } + } + impl ::std::ops::Deref for compilation { + type Target = LazyCompilationPluginInner; + fn deref(&self) -> &Self::Target { + &self.inner + } + } + impl AsyncSeries2 for compilation { + #[allow( + clippy::async_yields_async, + clippy::diverging_sub_expression, + clippy::let_unit_value, + clippy::no_effect_underscore_binding, + clippy::shadow_same, + clippy::type_complexity, + clippy::type_repetition_in_bounds, + clippy::used_underscore_binding + )] + fn run<'life0, 'life1, 'life2, 'async_trait>( + &'life0 self, + compilation: &'life1 mut Compilation, + params: &'life2 mut CompilationParams, + ) -> ::core::pin::Pin< + Box> + ::core::marker::Send + 'async_trait>, + > + where + 'life0: 'async_trait, + 'life1: 'async_trait, + 'life2: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::> { + return __ret; + } + let __self = self; + let __ret: Result<()> = { + LazyCompilationPlugin::compilation( + &LazyCompilationPlugin::from_inner(&__self.inner), + compilation, + params, + ) + .await + }; + #[allow(unreachable_code)] + __ret + }) + } + } + impl Plugin for LazyCompilationPlugin { + #[allow( + clippy::async_yields_async, + clippy::diverging_sub_expression, + clippy::let_unit_value, + clippy::no_effect_underscore_binding, + clippy::shadow_same, + clippy::type_complexity, + clippy::type_repetition_in_bounds, + clippy::used_underscore_binding + )] + fn normal_module_factory_module<'life0, 'life1, 'life2, 'async_trait>( + &'life0 self, + _ctx: PluginContext, + module: BoxModule, + args: &'life1 mut NormalModuleCreateData<'life2>, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + 'life1: 'async_trait, + 'life2: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + if let ::core::option::Option::Some(__ret) = + ::core::option::Option::None:: + { + return __ret; + } + let __self = self; + let _ctx = _ctx; + let module = module; + let __ret: PluginNormalModuleFactoryModuleHookOutput = { + if let Some(query) = &args.resource_resolve_data.resource_query + && query.contains("lazy-compilation-proxy-dep") + { + let remaining_query = query.clone().replace("lazy-compilation-proxy-dep", ""); + args.resource_resolve_data.resource_query = + if remaining_query.is_empty() || remaining_query == "?" { + None + } else { + Some(remaining_query) + }; + return Ok(module); + } + let create_data = args.create_data; + let dep_type = create_data.dependency.dependency_type(); + let is_imports = match dep_type { + DependencyType::DynamicImport + | DependencyType::DynamicImportEager + | DependencyType::ContextElement => true, + _ => false, + }; + let is_entries = match dep_type { + DependencyType::Entry => true, + _ => false, + }; + #[allow(clippy::if_same_then_else)] + if match dep_type { + DependencyType::ModuleHotAccept + | DependencyType::ModuleHotDecline + | DependencyType::ImportMetaHotAccept + | DependencyType::ImportMetaHotDecline => true, + _ => false, + } { + return Ok(module); + } else if !is_entries && !is_imports { + return Ok(module); + } + if !__self.entries && is_entries { + return Ok(module); + } + if !__self.imports && is_imports { + return Ok(module); + } + if WEBPACK_DEV_SERVER_CLIENT_RE.test(args.resolve_data_request) + || !__self.check_test(&module) + { + return Ok(module); + } + let mut backend = __self.backend.lock().await; + let module_identifier = module.identifier(); + let info = backend + .module( + module_identifier, + args.resource_resolve_data.resource.clone(), + ) + .await?; + match module_identifier { + tmp => { + { + ::std::io::_eprint(format_args!( + "[{0}:{1}:{2}] {3} = {4:#?}\n", + "crates/rspack_plugin_lazy_compilation/src/plugin.rs", + 151u32, + 5u32, + "module_identifier", + &tmp + )); + }; + tmp + } + }; + Ok(Box::new(LazyCompilationProxyModule::new( + module_identifier, + ProxyCreateData::new(args), + args.resolve_data_request.to_string(), + __self.cacheable, + info.active, + info.data, + info.client, + )) as BoxModule) + }; + #[allow(unreachable_code)] + __ret + }) + } + } +} diff --git a/crates/rspack_plugin_lazy_compilation/src/backend.rs b/crates/rspack_plugin_lazy_compilation/src/backend.rs new file mode 100644 index 00000000000..87f2e8d07f6 --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/backend.rs @@ -0,0 +1,14 @@ +use rspack_core::ModuleIdentifier; +use rspack_error::Result; + +pub struct ModuleInfo { + pub active: bool, + pub data: String, + pub client: String, +} + +#[async_trait::async_trait] +pub trait Backend: std::fmt::Debug + Send + Sync { + async fn module(&mut self, original_module: ModuleIdentifier, path: String) + -> Result; +} diff --git a/crates/rspack_plugin_lazy_compilation/src/dependency.rs b/crates/rspack_plugin_lazy_compilation/src/dependency.rs new file mode 100644 index 00000000000..d527db351fc --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/dependency.rs @@ -0,0 +1,87 @@ +use std::path::PathBuf; + +use rspack_core::{ + AsContextDependency, AsDependencyTemplate, Context, Dependency, DependencyCategory, DependencyId, + DependencyType, ModuleDependency, NormalModuleCreateData, Resolve, ResourceData, +}; +use rspack_error::Diagnostic; +use rspack_identifier::Identifier; +use rustc_hash::FxHashSet as HashSet; + +#[derive(Debug, Clone)] +pub(crate) struct ProxyCreateData { + pub resolve_options: Option>, + pub resource_resolve_data: ResourceData, + pub context: Context, + pub issuer: Option>, + pub issuer_identifier: Option, + pub file_dependencies: HashSet, + pub context_dependencies: HashSet, + pub missing_dependencies: HashSet, + pub diagnostics: Vec, +} + +impl ProxyCreateData { + pub(crate) fn new(module_create_data: &NormalModuleCreateData) -> Self { + Self { + resolve_options: module_create_data.create_data.resolve_options.clone(), + resource_resolve_data: module_create_data.resource_resolve_data.clone(), + context: module_create_data.context.clone(), + issuer: module_create_data.create_data.issuer.clone(), + issuer_identifier: module_create_data.create_data.issuer_identifier, + file_dependencies: module_create_data.create_data.file_dependencies.clone(), + context_dependencies: module_create_data.create_data.context_dependencies.clone(), + missing_dependencies: module_create_data.create_data.missing_dependencies.clone(), + diagnostics: module_create_data.diagnostics.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct LazyCompilationDependency { + id: DependencyId, + pub original_module_create_data: ProxyCreateData, + request: String, +} + +impl LazyCompilationDependency { + pub fn new(original_module_create_data: ProxyCreateData) -> Self { + let request = format!( + "{}?lazy-compilation-proxy-dep", + &original_module_create_data.resource_resolve_data.resource + ); + + Self { + id: DependencyId::new(), + original_module_create_data, + request, + } + } +} + +impl ModuleDependency for LazyCompilationDependency { + fn request(&self) -> &str { + &self.request + } +} + +impl AsDependencyTemplate for LazyCompilationDependency {} +impl AsContextDependency for LazyCompilationDependency {} + +impl Dependency for LazyCompilationDependency { + fn dependency_debug_name(&self) -> &'static str { + "lazy compilation dependency" + } + + fn id(&self) -> &rspack_core::DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Esm + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::LazyImport + } +} diff --git a/crates/rspack_plugin_lazy_compilation/src/factory.rs b/crates/rspack_plugin_lazy_compilation/src/factory.rs new file mode 100644 index 00000000000..c5dde4621f1 --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/factory.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use rspack_core::{ + ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, NormalModuleFactory, +}; +use rspack_error::Result; + +use crate::dependency::LazyCompilationDependency; + +#[derive(Debug)] +pub(crate) struct LazyCompilationDependencyFactory { + normal_module_factory: Arc, +} + +impl LazyCompilationDependencyFactory { + pub fn new(normal_module_factory: Arc) -> Self { + Self { + normal_module_factory, + } + } +} + +#[async_trait::async_trait] +impl ModuleFactory for LazyCompilationDependencyFactory { + async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { + let dep: &LazyCompilationDependency = data + .dependency + .as_any() + .downcast_ref() + .expect("should be lazy compile dependency"); + + let proxy_data = &dep.original_module_create_data; + + let dep = dep.clone(); + + let mut create_data = ModuleFactoryCreateData { + resolve_options: proxy_data.resolve_options.clone(), + context: proxy_data.context.clone(), + dependency: Box::new(dep), + issuer: proxy_data.issuer.clone(), + issuer_identifier: proxy_data.issuer_identifier, + file_dependencies: proxy_data.file_dependencies.clone(), + context_dependencies: proxy_data.context_dependencies.clone(), + missing_dependencies: proxy_data.missing_dependencies.clone(), + diagnostics: proxy_data.diagnostics.clone(), + }; + + self.normal_module_factory.create(&mut create_data).await + } +} diff --git a/crates/rspack_plugin_lazy_compilation/src/lib.rs b/crates/rspack_plugin_lazy_compilation/src/lib.rs new file mode 100644 index 00000000000..c30623e5320 --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/lib.rs @@ -0,0 +1,6 @@ +#![feature(let_chains)] +pub mod backend; +mod dependency; +mod factory; +mod module; +pub mod plugin; diff --git a/crates/rspack_plugin_lazy_compilation/src/module.rs b/crates/rspack_plugin_lazy_compilation/src/module.rs new file mode 100644 index 00000000000..2480858878d --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/module.rs @@ -0,0 +1,323 @@ +use std::{hash::Hash, path::PathBuf, sync::Arc}; + +use rspack_core::{ + impl_build_info_meta, module_namespace_promise, + rspack_sources::{RawSource, Source}, + AsyncDependenciesBlock, AsyncDependenciesBlockIdentifier, BoxDependency, BuildContext, BuildInfo, + BuildMeta, BuildResult, CodeGenerationResult, Compilation, ConcatenationScope, Context, + DependenciesBlock, DependencyId, Module, ModuleIdentifier, ModuleType, RuntimeGlobals, + RuntimeSpec, SourceType, TemplateContext, +}; +use rspack_error::{Diagnosable, Diagnostic, Result}; +use rspack_identifier::Identifiable; +use rspack_plugin_javascript::dependency::CommonJsRequireDependency; +use rspack_util::source_map::{ModuleSourceMapConfig, SourceMapKind}; +use rustc_hash::FxHashSet; + +use crate::dependency::{LazyCompilationDependency, ProxyCreateData}; + +static MODULE_TYPE: ModuleType = ModuleType::Js; +static SOURCE_TYPE: [SourceType; 1] = [SourceType::JavaScript]; + +#[derive(Debug)] +pub(crate) struct LazyCompilationProxyModule { + build_info: Option, + build_meta: Option, + original_module: ModuleIdentifier, + cacheable: bool, + + readable_identifier: String, + identifier: ModuleIdentifier, + + blocks: Vec, + dependencies: Vec, + + source_map_kind: SourceMapKind, + create_data: ProxyCreateData, + pub request: String, + + pub active: bool, + pub data: String, + pub client: String, +} + +impl Hash for LazyCompilationProxyModule { + fn hash(&self, state: &mut H) { + self.build_meta.hash(state); + self.original_module.hash(state); + self.readable_identifier.hash(state); + self.identifier.hash(state); + self.blocks.hash(state); + self.dependencies.hash(state); + } +} + +impl PartialEq for LazyCompilationProxyModule { + fn eq(&self, other: &Self) -> bool { + self.original_module == other.original_module + && self.readable_identifier == other.readable_identifier + && self.identifier == other.identifier + } +} + +impl Eq for LazyCompilationProxyModule {} + +impl ModuleSourceMapConfig for LazyCompilationProxyModule { + fn get_source_map_kind(&self) -> &SourceMapKind { + &self.source_map_kind + } + + fn set_source_map_kind(&mut self, source_map: SourceMapKind) { + self.source_map_kind = source_map; + } +} + +impl LazyCompilationProxyModule { + pub(crate) fn new( + original_module: ModuleIdentifier, + create_data: ProxyCreateData, + request: String, + cacheable: bool, + active: bool, + data: String, + client: String, + ) -> Self { + let readable_identifier = format!( + "lazy-compilation-proxy|{}", + create_data.context.shorten(&original_module) + ); + let identifier = format!("lazy-compilation-proxy|{original_module}").into(); + + Self { + build_info: None, + build_meta: None, + cacheable, + original_module, + create_data, + readable_identifier, + identifier, + source_map_kind: SourceMapKind::None, + blocks: vec![], + dependencies: vec![], + active, + request, + client, + data, + } + } +} + +impl Diagnosable for LazyCompilationProxyModule { + fn add_diagnostic(&self, _diagnostic: Diagnostic) { + unimplemented!() + } + fn add_diagnostics(&self, _diagnostics: Vec) { + unimplemented!() + } +} + +#[async_trait::async_trait] +impl Module for LazyCompilationProxyModule { + impl_build_info_meta!(); + + fn source_types(&self) -> &[SourceType] { + &SOURCE_TYPE + } + + fn module_type(&self) -> &ModuleType { + &MODULE_TYPE + } + + fn size(&self, _source_type: &SourceType) -> f64 { + 200f64 + } + + fn original_source(&self) -> Option<&dyn Source> { + None + } + + fn readable_identifier(&self, _context: &Context) -> std::borrow::Cow { + std::borrow::Cow::Borrowed(&self.readable_identifier) + } + + fn get_diagnostics(&self) -> Vec { + vec![] + } + + async fn build( + &mut self, + _build_context: BuildContext<'_>, + _compilation: Option<&Compilation>, + ) -> Result { + let client_dep = CommonJsRequireDependency::new(self.client.clone(), None, 0, 0, false); + let mut dependencies = vec![]; + let mut blocks = vec![]; + + dependencies.push(Box::new(client_dep) as BoxDependency); + + if self.active { + let dep = LazyCompilationDependency::new(self.create_data.clone()); + + blocks.push(AsyncDependenciesBlock::new( + self.identifier, + None, + None, + vec![Box::new(dep)], + )); + } + + let mut files = FxHashSet::default(); + files.extend(self.create_data.file_dependencies.clone()); + files.insert(PathBuf::from( + &self.create_data.resource_resolve_data.resource, + )); + + Ok(BuildResult { + build_info: BuildInfo { + cacheable: self.cacheable, + file_dependencies: files, + ..Default::default() + }, + build_meta: BuildMeta::default(), + analyze_result: Default::default(), + dependencies, + blocks, + optimization_bailouts: vec![], + }) + } + + fn code_generation( + &self, + compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + mut concatenation_scope: Option, + ) -> Result { + let mut runtime_requirements = RuntimeGlobals::empty(); + runtime_requirements.insert(RuntimeGlobals::MODULE); + runtime_requirements.insert(RuntimeGlobals::REQUIRE); + + let client_dep_id = self.dependencies[0]; + let module_graph = &compilation.get_module_graph(); + let chunk_graph = &compilation.chunk_graph; + + let client_module = module_graph + .module_identifier_by_dependency_id(&client_dep_id) + .expect("should have module"); + + let block = self.blocks.first(); + + let client = format!( + "var client = __webpack_require__(\"{}\");\nvar data = \"{}\"", + chunk_graph + .get_module_id(*client_module) + .as_ref() + .expect("should have module id"), + self.data + ); + + let keep_active = format!( + "var dispose = client.keepAlive({{ data: data, active: {}, module: module, onError: onError }})", + block.is_some() + ); + + let source = if let Some(block_id) = block { + let block = module_graph + .block_by_id(block_id) + .expect("should have block"); + + let dep_id = block.get_dependencies()[0]; + let module = module_graph + .module_identifier_by_dependency_id(&dep_id) + .expect("should have module"); + + let mut template_ctx = TemplateContext { + compilation, + module: module_graph + .module_by_identifier(module) + .expect("should have module") + .as_ref(), + runtime_requirements: &mut runtime_requirements, + init_fragments: &mut vec![], + runtime: None, + concatenation_scope: concatenation_scope.as_mut(), + }; + + RawSource::from(format!( + "{client} + module.exports = {}; + if (module.hot) {{ + module.hot.accept(); + module.hot.accept(\"{}\", function() {{ module.hot.invalidate(); }}); + module.hot.dispose(function(data) {{ delete data.resolveSelf; dispose(data); }}); + if (module.hot.data && module.hot.data.resolveSelf) + module.hot.data.resolveSelf(module.exports); + }} + function onError() {{ /* ignore */ }} + {} + ", + module_namespace_promise( + &mut template_ctx, + &dep_id, + Some(block_id), + &self.request, + "import()", + false + ), + chunk_graph + .get_module_id(*module) + .as_ref() + .expect("should have module id"), + keep_active, + )) + } else { + RawSource::from(format!( + "{} + var resolveSelf, onError; + module.exports = new Promise(function(resolve, reject) {{ resolveSelf = resolve; onError = reject; }}); + if (module.hot) {{ + module.hot.accept(); + if (module.hot.data && module.hot.data.resolveSelf) module.hot.data.resolveSelf(module.exports); + module.hot.dispose(function(data) {{ data.resolveSelf = resolveSelf; dispose(data); }}); + }} + {} + ", + client, + keep_active + )) + }; + + let mut codegen_result = CodeGenerationResult::default().with_javascript(Arc::new(source)); + codegen_result.runtime_requirements = runtime_requirements; + codegen_result.set_hash( + &compilation.options.output.hash_function, + &compilation.options.output.hash_digest, + &compilation.options.output.hash_salt, + ); + + Ok(codegen_result) + } +} + +impl Identifiable for LazyCompilationProxyModule { + fn identifier(&self) -> rspack_identifier::Identifier { + self.identifier + } +} + +impl DependenciesBlock for LazyCompilationProxyModule { + fn add_block_id(&mut self, block: rspack_core::AsyncDependenciesBlockIdentifier) { + self.blocks.push(block); + } + + fn get_blocks(&self) -> &[rspack_core::AsyncDependenciesBlockIdentifier] { + &self.blocks + } + + fn add_dependency_id(&mut self, dependency: rspack_core::DependencyId) { + self.dependencies.push(dependency); + } + + fn get_dependencies(&self) -> &[rspack_core::DependencyId] { + &self.dependencies + } +} diff --git a/crates/rspack_plugin_lazy_compilation/src/plugin.rs b/crates/rspack_plugin_lazy_compilation/src/plugin.rs new file mode 100644 index 00000000000..54b0dd8f8ac --- /dev/null +++ b/crates/rspack_plugin_lazy_compilation/src/plugin.rs @@ -0,0 +1,162 @@ +use std::sync::Arc; + +use once_cell::sync::Lazy; +use rspack_core::{ + ApplyContext, BoxModule, Compilation, CompilationParams, CompilerOptions, DependencyType, + ModuleFactory, NormalModuleCreateData, Plugin, PluginContext, + PluginNormalModuleFactoryModuleHookOutput, +}; +use rspack_error::Result; +use rspack_hook::{plugin, plugin_hook, AsyncSeries2}; +use rspack_regex::RspackRegex; +use tokio::sync::Mutex; + +use crate::{ + backend::Backend, dependency::ProxyCreateData, factory::LazyCompilationDependencyFactory, + module::LazyCompilationProxyModule, +}; + +static WEBPACK_DEV_SERVER_CLIENT_RE: Lazy = Lazy::new(|| { + RspackRegex::new( + r#"(webpack|rspack)[/\\]hot[/\\]|(webpack|rspack)-dev-server[/\\]client|(webpack|rspack)-hot-middleware[/\\]client"#, + ) + .expect("should compile regex") +}); + +#[plugin] +#[derive(Debug)] +pub struct LazyCompilationPlugin { + backend: Mutex, + entries: bool, // enable for entries + imports: bool, // enable for imports + test: Option, + cacheable: bool, +} + +impl LazyCompilationPlugin { + pub fn new( + cacheable: bool, + backend: T, + test: Option, + entries: bool, + imports: bool, + ) -> Self { + Self::new_inner(Mutex::new(backend), entries, imports, test, cacheable) + } + + fn check_test(&self, module: &BoxModule) -> bool { + if let Some(test) = &self.test { + test.test(&module.name_for_condition().unwrap_or("".into())) + } else { + true + } + } +} + +#[plugin_hook(AsyncSeries2 for LazyCompilationPlugin )] +async fn compilation( + &self, + compilation: &mut Compilation, + params: &mut CompilationParams, +) -> Result<()> { + compilation.set_dependency_factory( + DependencyType::LazyImport, + Arc::new(LazyCompilationDependencyFactory::new( + params.normal_module_factory.clone(), + )) as Arc, + ); + + Ok(()) +} + +#[async_trait::async_trait] +impl Plugin for LazyCompilationPlugin { + fn apply( + &self, + _ctx: PluginContext<&mut ApplyContext>, + _options: &mut CompilerOptions, + ) -> Result<()> { + Ok(()) + } + + async fn normal_module_factory_module( + &self, + _ctx: PluginContext, + module: BoxModule, + args: &mut NormalModuleCreateData<'_>, + ) -> PluginNormalModuleFactoryModuleHookOutput { + if let Some(query) = &args.resource_resolve_data.resource_query + && query.contains("lazy-compilation-proxy-dep") + { + let remaining_query = query.clone().replace("lazy-compilation-proxy-dep", ""); + + args.resource_resolve_data.resource_query = + if remaining_query.is_empty() || remaining_query == "?" { + None + } else { + Some(remaining_query) + }; + + return Ok(module); + } + + let create_data = args.create_data; + let dep_type = create_data.dependency.dependency_type(); + + let is_imports = matches!( + dep_type, + DependencyType::DynamicImport + | DependencyType::DynamicImportEager + | DependencyType::ContextElement + ); + let is_entries = matches!(dep_type, DependencyType::Entry); + + #[allow(clippy::if_same_then_else)] + if matches!( + dep_type, + DependencyType::ModuleHotAccept + | DependencyType::ModuleHotDecline + | DependencyType::ImportMetaHotAccept + | DependencyType::ImportMetaHotDecline + ) { + // TODO: we cannot access module graph at this stage + // if hmr point to a module that is already been dyn imported + // eg: import('./foo'); module.hot.accept('./foo') + // however we cannot access module graph at this time, so we cannot + // detect this case easily + return Ok(module); + } else if !is_entries && !is_imports { + return Ok(module); + } + + if !self.entries && is_entries { + return Ok(module); + } + if !self.imports && is_imports { + return Ok(module); + } + + if WEBPACK_DEV_SERVER_CLIENT_RE.test(args.resolve_data_request) || !self.check_test(&module) { + return Ok(module); + } + + let mut backend = self.backend.lock().await; + let module_identifier = module.identifier(); + let info = backend + .module( + module_identifier, + args.resource_resolve_data.resource.clone(), + ) + .await?; + dbg!(module_identifier); + Ok(Box::new(LazyCompilationProxyModule::new( + module_identifier, + ProxyCreateData::new(args), + args.resolve_data_request.to_string(), + self.cacheable, + info.active, + info.data, + info.client, + )) as BoxModule) + } +} diff --git a/crates/rspack_plugin_runtime/src/lazy_compilation.rs b/crates/rspack_plugin_runtime/src/lazy_compilation.rs deleted file mode 100644 index ea2dccaff7f..00000000000 --- a/crates/rspack_plugin_runtime/src/lazy_compilation.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::borrow::Cow; -use std::hash::Hash; - -use async_trait::async_trait; -use rspack_core::{ - impl_build_info_meta, impl_source_map_config, - rspack_sources::{RawSource, Source, SourceExt}, - AsyncDependenciesBlockIdentifier, BuildInfo, BuildMeta, Compilation, ConcatenationScope, - DependenciesBlock, DependencyId, Module, ModuleType, NormalModuleCreateData, Plugin, - PluginContext, PluginNormalModuleFactoryCreateModuleHookOutput, RuntimeGlobals, RuntimeSpec, - SourceType, -}; -use rspack_core::{CodeGenerationResult, Context, ModuleIdentifier}; -use rspack_error::{impl_empty_diagnosable_trait, Result}; -use rspack_identifier::Identifiable; - -#[impl_source_map_config] -#[derive(Debug)] -pub struct LazyCompilationProxyModule { - dependencies: Vec, - blocks: Vec, - pub module_identifier: ModuleIdentifier, - build_info: Option, - build_meta: Option, -} - -impl DependenciesBlock for LazyCompilationProxyModule { - fn add_block_id(&mut self, block: AsyncDependenciesBlockIdentifier) { - self.blocks.push(block) - } - - fn get_blocks(&self) -> &[AsyncDependenciesBlockIdentifier] { - &self.blocks - } - - fn add_dependency_id(&mut self, dependency: DependencyId) { - self.dependencies.push(dependency) - } - - fn get_dependencies(&self) -> &[DependencyId] { - &self.dependencies - } -} - -impl Module for LazyCompilationProxyModule { - impl_build_info_meta!(); - - fn module_type(&self) -> &ModuleType { - &ModuleType::Js - } - - fn source_types(&self) -> &[SourceType] { - &[SourceType::JavaScript] - } - - fn original_source(&self) -> Option<&dyn Source> { - None - } - fn get_diagnostics(&self) -> Vec { - vec![] - } - fn readable_identifier(&self, context: &Context) -> Cow { - Cow::Owned(context.shorten(&self.identifier())) - } - - fn size(&self, _source_type: &SourceType) -> f64 { - 200.0 - } - - fn code_generation( - &self, - compilation: &Compilation, - _runtime: Option<&RuntimeSpec>, - _: Option, - ) -> Result { - let mut cgr = CodeGenerationResult::default(); - cgr.runtime_requirements.insert(RuntimeGlobals::LOAD_SCRIPT); - cgr.runtime_requirements.insert(RuntimeGlobals::MODULE); - cgr.add( - SourceType::JavaScript, - RawSource::from( - include_str!("runtime/lazy_compilation.js") - // TODO - .replace("$CHUNK_ID$", self.module_identifier.to_string().as_str()) - .replace("$MODULE_ID$", self.module_identifier.to_string().as_str()), - ) - .boxed(), - ); - cgr.set_hash( - &compilation.options.output.hash_function, - &compilation.options.output.hash_digest, - &compilation.options.output.hash_salt, - ); - Ok(cgr) - } -} - -impl Identifiable for LazyCompilationProxyModule { - fn identifier(&self) -> ModuleIdentifier { - self.module_identifier - } -} - -impl_empty_diagnosable_trait!(LazyCompilationProxyModule); - -impl Hash for LazyCompilationProxyModule { - fn hash(&self, state: &mut H) { - "__rspack_internal__LazyCompilationProxyModule".hash(state); - self.identifier().hash(state); - } -} - -impl PartialEq for LazyCompilationProxyModule { - fn eq(&self, other: &Self) -> bool { - self.identifier() == other.identifier() - } -} - -impl Eq for LazyCompilationProxyModule {} - -#[derive(Debug)] -pub struct LazyCompilationPlugin; - -#[async_trait] -impl Plugin for LazyCompilationPlugin { - fn name(&self) -> &'static str { - "LazyCompilationPlugin" - } - - async fn normal_module_factory_create_module( - &self, - _ctx: PluginContext, - _args: &mut NormalModuleCreateData<'_>, - ) -> PluginNormalModuleFactoryCreateModuleHookOutput { - // if args.indentfiler.contains("rspack-dev-client") - // || args.lazy_visit_modules.contains(args.indentfiler.as_str()) - // { - // return Ok(None); - // } - // if matches!( - // args.dependency_type, - // DependencyType::DynamicImport | DependencyType::Entry - // ) { - // return Ok(Some(Box::new(LazyCompilationProxyModule { - // module_identifier: args.indentfiler, - // dependencies: Vec::new(), - // blocks: Vec::new(), - // }))); - // } - - Ok(None) - } -} diff --git a/crates/rspack_plugin_runtime/src/lib.rs b/crates/rspack_plugin_runtime/src/lib.rs index 4f0b52f6469..db1ffb6e79f 100644 --- a/crates/rspack_plugin_runtime/src/lib.rs +++ b/crates/rspack_plugin_runtime/src/lib.rs @@ -1,8 +1,6 @@ #![feature(get_mut_unchecked)] mod helpers; pub use helpers::*; -mod lazy_compilation; -pub use lazy_compilation::LazyCompilationPlugin; mod common_js_chunk_format; pub use common_js_chunk_format::CommonJsChunkFormatPlugin; mod runtime_plugin; diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index f3c75fd9ad9..b00fbd8fca8 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -217,34 +217,6 @@ export class RspackDevServer extends WebpackDevServer { private override setupMiddlewares() { const middlewares: WebpackDevServer.Middleware[] = []; - const compilers = - this.compiler instanceof MultiCompiler - ? this.compiler.compilers - : [this.compiler]; - - compilers.forEach(compiler => { - if (compiler.options.experiments.lazyCompilation) { - middlewares.push({ - // @ts-expect-error - middleware: (req, res) => { - if (req.url.indexOf("/lazy-compilation-web/") > -1) { - const path = req.url.replace("/lazy-compilation-web/", ""); - if (fs.existsSync(path)) { - compiler.rebuild(new Set([path]), new Set(), error => { - if (error) { - throw error; - } - res.write(""); - res.end(); - console.log("lazy compiler success"); - }); - } - } - } - }); - } - }); - middlewares.forEach(middleware => { if (typeof middleware === "function") { // @ts-expect-error diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index 2b942fb5504..28d3ba961ab 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -1071,6 +1071,7 @@ class Compiler { }); return; } + this.hooks.shutdown.callAsync(err => { if (err) return callback(err); this.cache.shutdown(callback); diff --git a/packages/rspack/src/Watching.ts b/packages/rspack/src/Watching.ts index d743771720c..d9a61f72bf4 100644 --- a/packages/rspack/src/Watching.ts +++ b/packages/rspack/src/Watching.ts @@ -195,6 +195,10 @@ export class Watching { this.#invalidate(); } + lazyCompilationInvalidate(files: Set) { + this.#invalidate(new Map(), new Map(), files, new Set()); + } + #invalidate( fileTimeInfoEntries?: Map, contextTimeInfoEntries?: Map, diff --git a/packages/rspack/src/builtin-plugin/index.ts b/packages/rspack/src/builtin-plugin/index.ts index e9296c0bd00..ebcffc7e9ba 100644 --- a/packages/rspack/src/builtin-plugin/index.ts +++ b/packages/rspack/src/builtin-plugin/index.ts @@ -54,6 +54,7 @@ export * from "./SwcJsMinimizerPlugin"; export * from "./SwcCssMinimizerPlugin"; export * from "./JsLoaderRspackPlugin"; +export * from "./lazy-compilation/plugin"; ///// DEPRECATED ///// import { RawBuiltins, RawCssModulesConfig } from "@rspack/binding"; diff --git a/packages/rspack/src/builtin-plugin/lazy-compilation/backend.ts b/packages/rspack/src/builtin-plugin/lazy-compilation/backend.ts new file mode 100644 index 00000000000..2b381d83745 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/lazy-compilation/backend.ts @@ -0,0 +1,229 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ + +"use strict"; + +import type { + IncomingMessage, + ServerResponse, + ServerOptions as ServerOptionsImport +} from "http"; +import type { AddressInfo, ListenOptions, Server, Socket } from "net"; +import type { Compiler } from "../.."; +import type { SecureContextOptions, TlsOptions } from "tls"; + +export interface LazyCompilationDefaultBackendOptions { + /** + * A custom client. + */ + client?: string; + + /** + * Specifies where to listen to from the server. + */ + listen?: number | ListenOptions | ((server: Server) => void); + + /** + * Specifies the protocol the client should use to connect to the server. + */ + protocol?: "http" | "https"; + + /** + * Specifies how to create the server handling the EventSource requests. + */ + server?: + | ServerOptionsImport + | ServerOptionsHttps + | (() => Server); +} + +export type ServerOptionsHttps< + Request extends typeof IncomingMessage = typeof IncomingMessage, + Response extends typeof ServerResponse = typeof ServerResponse +> = SecureContextOptions & TlsOptions & ServerOptionsImport; + +/** + * @param {Omit & { client: NonNullable}} options additional options for the backend + * @returns {BackendHandler} backend + */ +const getBackend = + (options: any) => + ( + compiler: Compiler, + callback: ( + err: any, + obj?: { + dispose: (callback: (err: any) => void) => void; + module: (args: { module: string; path: string }) => { + data: string; + client: string; + active: boolean; + }; + } + ) => void + ) => { + const logger = compiler.getInfrastructureLogger("LazyCompilationBackend"); + const activeModules = new Map(); + const filesByKey: Map = new Map(); + const prefix = "/lazy-compilation-using-"; + const isHttps = + options.protocol === "https" || + (typeof options.server === "object" && + ("key" in options.server || "pfx" in options.server)); + + const createServer = + typeof options.server === "function" + ? options.server + : (() => { + const http = isHttps ? require("https") : require("http"); + return http.createServer.bind(http, options.server); + })(); + const listen = + typeof options.listen === "function" + ? options.listen + : (server: Server) => { + let listen = options.listen; + if (typeof listen === "object" && !("port" in listen)) + listen = { ...listen, port: undefined }; + server.listen(listen); + }; + + const protocol = options.protocol || (isHttps ? "https" : "http"); + + const requestListener = (req: any, res: ServerResponse) => { + const keys = req.url.slice(prefix.length).split("@"); + req.socket.on("close", () => { + setTimeout(() => { + for (const key of keys) { + const oldValue = activeModules.get(key) || 0; + activeModules.set(key, oldValue - 1); + if (oldValue === 1) { + logger.log( + `${key} is no longer in use. Next compilation will skip this module.` + ); + } + } + }, 120000); + }); + req.socket.setNoDelay(true); + res.writeHead(200, { + "content-type": "text/event-stream", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "*", + "Access-Control-Allow-Headers": "*" + }); + res.write("\n"); + const moduleActivated = []; + for (const key of keys) { + const oldValue = activeModules.get(key) || 0; + activeModules.set(key, oldValue + 1); + if (oldValue === 0) { + logger.log(`${key} is now in use and will be compiled.`); + moduleActivated.push(key); + } + } + + if (moduleActivated.length && compiler.watching) { + compiler.watching.lazyCompilationInvalidate( + new Set(moduleActivated.map(key => filesByKey.get(key)!)) + ); + } + }; + + const server = createServer() as Server; + server.on("request", requestListener); + + let isClosing = false; + const sockets: Set = new Set(); + server.on("connection", socket => { + sockets.add(socket); + socket.on("close", () => { + sockets.delete(socket); + }); + if (isClosing) socket.destroy(); + }); + server.on("clientError", e => { + if (e.message !== "Server is disposing") logger.warn(e); + }); + server.on("listening", (err: any) => { + if (err) return callback(err); + const addr = server.address() as AddressInfo; + if (typeof addr === "string") + throw new Error("addr must not be a string"); + const urlBase = + addr.address === "::" || addr.address === "0.0.0.0" + ? `${protocol}://localhost:${addr.port}` + : addr.family === "IPv6" + ? `${protocol}://[${addr.address}]:${addr.port}` + : `${protocol}://${addr.address}:${addr.port}`; + logger.log( + `Server-Sent-Events server for lazy compilation open at ${urlBase}.` + ); + + const result = { + dispose(callback: any) { + isClosing = true; + // Removing the listener is a workaround for a memory leak in node.js + server.off("request", requestListener); + server.close(err => { + callback(err); + }); + for (const socket of sockets) { + socket.destroy(new Error("Server is disposing")); + } + }, + module({ + module: originalModule, + path + }: { + module: string; + path: string; + }) { + const key = `${encodeURIComponent( + originalModule.replace(/\\/g, "/").replace(/@/g, "_") + ).replace(/%(2F|3A|24|26|2B|2C|3B|3D|3A)/g, decodeURIComponent)}`; + filesByKey.set(key, path); + const active = activeModules.get(key) > 0; + return { + client: `${options.client}?${encodeURIComponent(urlBase + prefix)}`, + data: key, + active + }; + } + }; + state.module = result.module; + state.dispose = result.dispose; + callback(null, result); + }); + listen(server); + }; + +export default getBackend; + +function unimplemented() { + throw new Error("access before initialization"); +} + +const state: { + module: typeof moduleImpl; + dispose: typeof dispose; +} = { + module: unimplemented as any, + dispose: unimplemented +}; + +export function dispose(callback: any) { + state.dispose(callback); + state.dispose = unimplemented; + state.module = unimplemented as any; +} + +export function moduleImpl(args: { module: string; path: string }): { + active: boolean; + data: string; + client: string; +} { + return state.module(args); +} diff --git a/packages/rspack/src/builtin-plugin/lazy-compilation/lazyCompilation.ts b/packages/rspack/src/builtin-plugin/lazy-compilation/lazyCompilation.ts new file mode 100644 index 00000000000..c847ce69d60 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/lazy-compilation/lazyCompilation.ts @@ -0,0 +1,18 @@ +import { BuiltinPluginName, RawRegexMatcher } from "@rspack/binding"; +import { create } from "../base"; + +export const BuiltinLazyCompilationPlugin = create( + BuiltinPluginName.LazyCompilation, + ( + module: (args: { module: string; path: string }) => { + active: boolean; + data: string; + client: string; + }, + cacheable: boolean, + entries: boolean, + imports: boolean, + test?: RawRegexMatcher + ) => ({ module, cacheable, imports, entries, test }), + "compilation" +); diff --git a/packages/rspack/src/builtin-plugin/lazy-compilation/plugin.ts b/packages/rspack/src/builtin-plugin/lazy-compilation/plugin.ts new file mode 100644 index 00000000000..a3b33e900c1 --- /dev/null +++ b/packages/rspack/src/builtin-plugin/lazy-compilation/plugin.ts @@ -0,0 +1,68 @@ +import type { Compiler } from "../.."; + +import { BuiltinLazyCompilationPlugin } from "./lazyCompilation"; +import getBackend, { + moduleImpl, + dispose, + LazyCompilationDefaultBackendOptions +} from "./backend"; +import { RawRegexMatcher } from "@rspack/binding"; + +export default class LazyCompilationPlugin { + cacheable: boolean; + entries: boolean; + imports: boolean; + test?: RawRegexMatcher; + backend?: LazyCompilationDefaultBackendOptions; + + constructor( + cacheable: boolean, + entries: boolean, + imports: boolean, + test?: RawRegexMatcher, + backend?: LazyCompilationDefaultBackendOptions + ) { + this.cacheable = cacheable; + this.entries = entries; + this.imports = imports; + this.test = test; + this.backend = backend; + } + + apply(compiler: Compiler) { + const backend = getBackend({ + ...this.backend, + client: require.resolve( + `../../../hot/lazy-compilation-${ + compiler.options.externalsPresets.node ? "node" : "web" + }.js` + ) + }); + + new BuiltinLazyCompilationPlugin( + moduleImpl, + this.cacheable, + this.entries, + this.imports, + this.test + ).apply(compiler); + + let initialized = false; + compiler.hooks.beforeCompile.tapAsync( + "LazyCompilationPlugin", + (_params, callback) => { + if (initialized) return callback(); + backend(compiler, (err, result) => { + if (err) return callback(err); + initialized = true; + callback(); + }); + } + ); + compiler.hooks.shutdown.tapAsync("LazyCompilationPlugin", callback => { + dispose(callback); + }); + } +} + +export { LazyCompilationPlugin }; diff --git a/packages/rspack/src/config/normalization.ts b/packages/rspack/src/config/normalization.ts index 0920d94c20e..8170b3dfd70 100644 --- a/packages/rspack/src/config/normalization.ts +++ b/packages/rspack/src/config/normalization.ts @@ -78,7 +78,8 @@ import type { NoParseOption, DevtoolNamespace, DevtoolModuleFilenameTemplate, - DevtoolFallbackModuleFilenameTemplate + DevtoolFallbackModuleFilenameTemplate, + LazyCompilationOptions } from "./zod"; export const getNormalizedRspackOptions = ( @@ -285,7 +286,11 @@ export const getNormalizedRspackOptions = ( }), plugins: nestedArray(config.plugins, p => [...p]), experiments: nestedConfig(config.experiments, experiments => ({ - ...experiments + ...experiments, + lazyCompilation: optionalNestedConfig( + experiments.lazyCompilation, + options => (options === true ? {} : options) + ) })), watch: config.watch, watchOptions: cloneObject(config.watchOptions), @@ -475,7 +480,7 @@ export interface ModuleOptionsNormalized { } export interface ExperimentsNormalized { - lazyCompilation?: boolean; + lazyCompilation?: false | LazyCompilationOptions; asyncWebAssembly?: boolean; outputModule?: boolean; newSplitChunks?: boolean; diff --git a/packages/rspack/src/config/zod.ts b/packages/rspack/src/config/zod.ts index 4353caa2710..6f952e4c9b6 100644 --- a/packages/rspack/src/config/zod.ts +++ b/packages/rspack/src/config/zod.ts @@ -6,6 +6,7 @@ import type * as webpackDevServer from "webpack-dev-server"; import { deprecatedWarn } from "../util"; import { Module } from "../Module"; import { Chunk } from "../Chunk"; +import { LazyCompilationDefaultBackendOptions } from "../builtin-plugin/lazy-compilation/backend"; //#region Name const name = z.string(); @@ -1104,8 +1105,18 @@ const rspackFutureOptions = z.strictObject({ }); export type RspackFutureOptions = z.infer; +const lazyCompilationOptions = z.object({ + cacheable: z.boolean().optional(), + imports: z.boolean().optional(), + entries: z.boolean().optional(), + test: z.instanceof(RegExp).optional(), + backend: z.custom().optional() +}); + +export type LazyCompilationOptions = z.infer; + const experiments = z.strictObject({ - lazyCompilation: z.boolean().optional(), + lazyCompilation: z.boolean().optional().or(lazyCompilationOptions), asyncWebAssembly: z.boolean().optional(), outputModule: z.boolean().optional(), topLevelAwait: z.boolean().optional(), diff --git a/packages/rspack/src/rspackOptionsApply.ts b/packages/rspack/src/rspackOptionsApply.ts index 9fadc79701e..a3a44d195cb 100644 --- a/packages/rspack/src/rspackOptionsApply.ts +++ b/packages/rspack/src/rspackOptionsApply.ts @@ -62,7 +62,8 @@ import { BundlerInfoRspackPlugin, ModuleConcatenationPlugin, EvalDevToolModulePlugin, - JsLoaderRspackPlugin + JsLoaderRspackPlugin, + LazyCompilationPlugin } from "./builtin-plugin"; import { deprecatedWarn } from "./util"; @@ -281,6 +282,24 @@ export class RspackOptionsApply { } } + if (options.experiments.lazyCompilation) { + const lazyOptions = options.experiments.lazyCompilation; + + new LazyCompilationPlugin( + // this is only for test + lazyOptions.cacheable ?? true, + lazyOptions.entries ?? true, + lazyOptions.imports ?? true, + lazyOptions.test + ? { + source: lazyOptions.test.source, + flags: lazyOptions.test.flags + } + : undefined, + lazyOptions.backend + ).apply(compiler); + } + if ( options.output.enabledLibraryTypes && options.output.enabledLibraryTypes.length > 0 diff --git a/webpack-test/hotCases/lazy-compilation/context/test.filter.js b/webpack-test/hotCases/lazy-compilation/context/test.filter.js deleted file mode 100644 index 5c32e24f1f8..00000000000 --- a/webpack-test/hotCases/lazy-compilation/context/test.filter.js +++ /dev/null @@ -1,3 +0,0 @@ - -module.exports = () => {return false} - \ No newline at end of file diff --git a/webpack-test/hotCases/lazy-compilation/context/webpack.config.js b/webpack-test/hotCases/lazy-compilation/context/webpack.config.js index 95c9eda7187..bca70843c7f 100644 --- a/webpack-test/hotCases/lazy-compilation/context/webpack.config.js +++ b/webpack-test/hotCases/lazy-compilation/context/webpack.config.js @@ -4,6 +4,7 @@ module.exports = { experiments: { lazyCompilation: { + cacheable: false, entries: false, imports: true, backend: { diff --git a/webpack-test/hotCases/lazy-compilation/https/test.filter.js b/webpack-test/hotCases/lazy-compilation/https/test.filter.js index 5c32e24f1f8..90e5eab00bc 100644 --- a/webpack-test/hotCases/lazy-compilation/https/test.filter.js +++ b/webpack-test/hotCases/lazy-compilation/https/test.filter.js @@ -1,3 +1,2 @@ -module.exports = () => {return false} - \ No newline at end of file +module.exports = () => { return false } diff --git a/webpack-test/hotCases/lazy-compilation/https/webpack.config.js b/webpack-test/hotCases/lazy-compilation/https/webpack.config.js index 11b07858d4d..ca3f7afdf0f 100644 --- a/webpack-test/hotCases/lazy-compilation/https/webpack.config.js +++ b/webpack-test/hotCases/lazy-compilation/https/webpack.config.js @@ -7,6 +7,7 @@ const path = require("path"); module.exports = { experiments: { lazyCompilation: { + cacheable: false, entries: false, backend: { server: { diff --git a/webpack-test/hotCases/lazy-compilation/module-test/test.filter.js b/webpack-test/hotCases/lazy-compilation/module-test/test.filter.js deleted file mode 100644 index 5c32e24f1f8..00000000000 --- a/webpack-test/hotCases/lazy-compilation/module-test/test.filter.js +++ /dev/null @@ -1,3 +0,0 @@ - -module.exports = () => {return false} - \ No newline at end of file diff --git a/webpack-test/hotCases/lazy-compilation/module-test/webpack.config.js b/webpack-test/hotCases/lazy-compilation/module-test/webpack.config.js index ede992e2343..559f2fcbd22 100644 --- a/webpack-test/hotCases/lazy-compilation/module-test/webpack.config.js +++ b/webpack-test/hotCases/lazy-compilation/module-test/webpack.config.js @@ -5,7 +5,8 @@ module.exports = { experiments: { lazyCompilation: { entries: false, - test: module => !/moduleB/.test(module.nameForCondition()) + cacheable: false, + test: /moduleA/ } } }; diff --git a/webpack-test/hotCases/lazy-compilation/only-entries/test.filter.js b/webpack-test/hotCases/lazy-compilation/only-entries/test.filter.js deleted file mode 100644 index 5c32e24f1f8..00000000000 --- a/webpack-test/hotCases/lazy-compilation/only-entries/test.filter.js +++ /dev/null @@ -1,3 +0,0 @@ - -module.exports = () => {return false} - \ No newline at end of file diff --git a/webpack-test/hotCases/lazy-compilation/simple/test.filter.js b/webpack-test/hotCases/lazy-compilation/simple/test.filter.js index 5c32e24f1f8..90e5eab00bc 100644 --- a/webpack-test/hotCases/lazy-compilation/simple/test.filter.js +++ b/webpack-test/hotCases/lazy-compilation/simple/test.filter.js @@ -1,3 +1,2 @@ -module.exports = () => {return false} - \ No newline at end of file +module.exports = () => { return false } diff --git a/webpack-test/hotCases/lazy-compilation/simple/webpack.config.js b/webpack-test/hotCases/lazy-compilation/simple/webpack.config.js index 148a5e40010..16ad9ede08e 100644 --- a/webpack-test/hotCases/lazy-compilation/simple/webpack.config.js +++ b/webpack-test/hotCases/lazy-compilation/simple/webpack.config.js @@ -4,7 +4,8 @@ module.exports = { experiments: { lazyCompilation: { - entries: false + entries: false, + cacheable: false } } }; diff --git a/webpack-test/hotCases/lazy-compilation/unrelated/test.filter.js b/webpack-test/hotCases/lazy-compilation/unrelated/test.filter.js deleted file mode 100644 index 5c32e24f1f8..00000000000 --- a/webpack-test/hotCases/lazy-compilation/unrelated/test.filter.js +++ /dev/null @@ -1,3 +0,0 @@ - -module.exports = () => {return false} - \ No newline at end of file