From aca19d4fe9541e95d6242b835ca4973b645cebcd Mon Sep 17 00:00:00 2001 From: hardfist Date: Mon, 9 Sep 2024 17:02:54 +0800 Subject: [PATCH 01/51] refactor(core): use `output_filesystem` with dynamic dispatch (#7817) * feat(chore): use dyn output_filesystem * fix: remove debug constraint * fix: remove debug --- crates/node_binding/src/compiler.rs | 4 +--- crates/node_binding/src/lib.rs | 6 ++++-- crates/rspack_core/src/compiler/hmr.rs | 6 +----- crates/rspack_core/src/compiler/mod.rs | 20 ++++++++------------ packages/rspack-cli/jest.config.js | 21 +++++++++------------ 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/crates/node_binding/src/compiler.rs b/crates/node_binding/src/compiler.rs index 2f7a8f71f14..d49f54c95b0 100644 --- a/crates/node_binding/src/compiler.rs +++ b/crates/node_binding/src/compiler.rs @@ -7,9 +7,7 @@ use std::{ }, }; -use rspack_fs_node::AsyncNodeWritableFileSystem; - -type CompilerInner = rspack_core::Compiler; +type CompilerInner = rspack_core::Compiler; /// `Compiler` struct that is `!Unpin`. pub(crate) struct Compiler(CompilerInner, PhantomPinned); diff --git a/crates/node_binding/src/lib.rs b/crates/node_binding/src/lib.rs index 7f17cbcd1f2..4afbc5021a0 100644 --- a/crates/node_binding/src/lib.rs +++ b/crates/node_binding/src/lib.rs @@ -67,8 +67,10 @@ impl Rspack { let rspack = rspack_core::Compiler::new( compiler_options, plugins, - AsyncNodeWritableFileSystem::new(output_filesystem) - .map_err(|e| Error::from_reason(format!("Failed to create writable filesystem: {e}",)))?, + Box::new( + AsyncNodeWritableFileSystem::new(output_filesystem) + .map_err(|e| Error::from_reason(format!("Failed to create writable filesystem: {e}",)))?, + ), Some(resolver_factory), Some(loader_resolver_factory), ); diff --git a/crates/rspack_core/src/compiler/hmr.rs b/crates/rspack_core/src/compiler/hmr.rs index 2a662dc6d26..e6c709d1ffc 100644 --- a/crates/rspack_core/src/compiler/hmr.rs +++ b/crates/rspack_core/src/compiler/hmr.rs @@ -3,7 +3,6 @@ use std::path::PathBuf; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rspack_collections::{Identifier, IdentifierMap}; use rspack_error::Result; -use rspack_fs::AsyncWritableFileSystem; use rspack_hash::RspackHashDigest; use rspack_sources::Source; use rustc_hash::FxHashSet as HashSet; @@ -12,10 +11,7 @@ use crate::{ fast_set, get_chunk_from_ukey, ChunkKind, Compilation, Compiler, ModuleExecutor, RuntimeSpec, }; -impl Compiler -where - T: AsyncWritableFileSystem + Send + Sync, -{ +impl Compiler { pub async fn rebuild( &mut self, changed_files: std::collections::HashSet, diff --git a/crates/rspack_core/src/compiler/mod.rs b/crates/rspack_core/src/compiler/mod.rs index 2c4e0d94854..6f38dc889b5 100644 --- a/crates/rspack_core/src/compiler/mod.rs +++ b/crates/rspack_core/src/compiler/mod.rs @@ -2,9 +2,9 @@ mod compilation; mod hmr; mod make; mod module_executor; - use std::sync::Arc; +use derivative::Derivative; use rspack_error::Result; use rspack_fs::AsyncWritableFileSystem; use rspack_futures::FuturesResults; @@ -49,13 +49,12 @@ pub struct CompilerHooks { pub asset_emitted: CompilerAssetEmittedHook, } -#[derive(Debug)] -pub struct Compiler -where - T: AsyncWritableFileSystem + Send + Sync, -{ +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Compiler { pub options: Arc, - pub output_filesystem: T, + #[derivative(Debug = "ignore")] + pub output_filesystem: Box, pub compilation: Compilation, pub plugin_driver: SharedPluginDriver, pub resolver_factory: Arc, @@ -67,15 +66,12 @@ where unaffected_modules_cache: Arc, } -impl Compiler -where - T: AsyncWritableFileSystem + Send + Sync, -{ +impl Compiler { #[instrument(skip_all)] pub fn new( options: CompilerOptions, plugins: Vec, - output_filesystem: T, + output_filesystem: Box, // no need to pass resolve_factory in rust api resolver_factory: Option>, loader_resolver_factory: Option>, diff --git a/packages/rspack-cli/jest.config.js b/packages/rspack-cli/jest.config.js index 0b2118a394f..40ee463f5f0 100644 --- a/packages/rspack-cli/jest.config.js +++ b/packages/rspack-cli/jest.config.js @@ -1,17 +1,14 @@ /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ const config = { - preset: "ts-jest", - testEnvironment: "../../scripts/test/patch-node-env.cjs", - testTimeout: process.env.CI ? 200000 : 30000, - testMatch: ["/tests/**/*.test.ts", "/tests/**/*.test.js"], - watchPathIgnorePatterns: ["/tests/.*/dist"], - extensionsToTreatAsEsm: [".mts"], - globals: { - "ts-jest": { - tsconfig: "/tests/tsconfig.json" - } - }, - prettierPath: require.resolve("prettier-2") + preset: 'ts-jest', + testEnvironment: '../../scripts/test/patch-node-env.cjs', + testTimeout: process.env.CI ? 200000 : 30000, + testMatch: ['/tests/**/*.test.ts', '/tests/**/*.test.js'], + watchPathIgnorePatterns: ['/tests/.*/dist'], + extensionsToTreatAsEsm: ['.mts'], + globals: {'ts-jest': {tsconfig: '/tests/tsconfig.json'}}, + cache: false, + prettierPath: require.resolve('prettier-2') }; module.exports = config; From 0075187ac60784da5a75a2bdac0d1ca2aeca5ddc Mon Sep 17 00:00:00 2001 From: Cong-Cong Pan Date: Mon, 9 Sep 2024 17:07:28 +0800 Subject: [PATCH 02/51] fix: should not transform source to buffer (#7821) * fix: transform source to buffer * chore: add test case --- .../src/source_map_dev_tool_plugin.rs | 30 ++++++------------- .../index.js | 0 .../rspack.config.js | 11 ++++++- .../test.config.js | 0 4 files changed, 19 insertions(+), 22 deletions(-) rename packages/rspack-test-tools/tests/configCases/plugins/{source-map-dev-tool-plugin-source-root => source-map-dev-tool-plugin}/index.js (100%) rename packages/rspack-test-tools/tests/configCases/plugins/{source-map-dev-tool-plugin-source-root => source-map-dev-tool-plugin}/rspack.config.js (61%) rename packages/rspack-test-tools/tests/configCases/plugins/{source-map-dev-tool-plugin-source-root => source-map-dev-tool-plugin}/test.config.js (100%) diff --git a/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs b/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs index 7c672d635bf..f58d8340b9e 100644 --- a/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs +++ b/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs @@ -342,18 +342,9 @@ impl SourceMapDevToolPlugin { mapped_sources .into_par_iter() - .map(|(filename, asset, source_map)| { - let code_buffer = { - let mut buffer = Vec::with_capacity(asset.size()); - asset.to_writer(&mut buffer).into_diagnostic()?; - buffer - }; - let source_map_buffer = match source_map { - Some(map) => { - let mut buffer = Vec::new(); - map.to_writer(&mut buffer).into_diagnostic()?; - Some(buffer) - } + .map(|(filename, source, source_map)| { + let source_map_json = match source_map { + Some(map) => Some(map.to_json().into_diagnostic()?), None => None, }; @@ -367,10 +358,7 @@ impl SourceMapDevToolPlugin { ) }) .clone(); - // convert to RawSource to reduce one time source map calculation when convert to JsCompatSource - let raw_source = RawSource::from(code_buffer).boxed(); - let Some(source_map_buffer) = source_map_buffer else { - asset.source = Some(raw_source); + let Some(source_map_json) = source_map_json else { return Ok(MappedAsset { asset: (filename, asset), source_map: None, @@ -440,7 +428,7 @@ impl SourceMapDevToolPlugin { }; asset.source = Some( ConcatSource::new([ - raw_source, + source.clone(), RawSource::from( current_source_mapping_url_comment.replace("[url]", &source_map_url), ) @@ -450,7 +438,7 @@ impl SourceMapDevToolPlugin { ); asset.info.related.source_map = Some(source_map_filename.clone()); } else { - asset.source = Some(raw_source); + asset.source = Some(source.clone()); } let mut source_map_asset_info = AssetInfo::default().with_development(true); if let Some(asset) = compilation.assets().get(filename.as_ref()) { @@ -458,7 +446,7 @@ impl SourceMapDevToolPlugin { source_map_asset_info.version = asset.info.version.clone(); } let source_map_asset = CompilationAsset::new( - Some(RawSource::from(source_map_buffer).boxed()), + Some(RawSource::from(source_map_json).boxed()), source_map_asset_info, ); Ok(MappedAsset { @@ -476,10 +464,10 @@ impl SourceMapDevToolPlugin { )) } }; - let base64 = rspack_base64::encode_to_string(&source_map_buffer); + let base64 = rspack_base64::encode_to_string(source_map_json.as_bytes()); asset.source = Some( ConcatSource::new([ - raw_source, + source.clone(), RawSource::from(current_source_mapping_url_comment.replace( "[url]", &format!("data:application/json;charset=utf-8;base64,{base64}"), diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/index.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/index.js similarity index 100% rename from packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/index.js rename to packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/index.js diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/rspack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/rspack.config.js similarity index 61% rename from packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/rspack.config.js rename to packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/rspack.config.js index fd79c85c76d..ecc18ea6d1e 100644 --- a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/rspack.config.js +++ b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/rspack.config.js @@ -19,6 +19,15 @@ module.exports = { }), new rspack.DefinePlugin({ CONTEXT: JSON.stringify(__dirname) - }) + }), + compiler => { + compiler.hooks.compilation.tap("PLUGIN", compilation => { + compilation.hooks.afterProcessAssets.tap("PLUGIN", assets => { + for (const asset of Object.values(assets)) { + expect(typeof asset.source()).toBe("string"); + } + }); + }); + } ] }; diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/test.config.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/test.config.js similarity index 100% rename from packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-source-root/test.config.js rename to packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin/test.config.js From 500c045e6d8df7c2fb50885f4e5011b58b55f077 Mon Sep 17 00:00:00 2001 From: Gengkun Date: Mon, 9 Sep 2024 17:31:35 +0800 Subject: [PATCH 03/51] fix: change javascript parser options in child compiler (#7812) --- .../src/options/raw_module/mod.rs | 52 ++++++++----- .../rspack_core/src/normal_module_factory.rs | 49 +++++++++--- crates/rspack_core/src/options/module.rs | 38 ++++++---- .../src/parser_plugin/worker_plugin.rs | 74 +++++++++++-------- crates/rspack_util/src/merge.rs | 15 +++- .../tests/__snapshots__/Defaults.test.js.snap | 51 ------------- .../child-entry.js | 6 ++ .../change-parser-in-child-compiler/img.png | 0 .../change-parser-in-child-compiler/index.js | 1 + .../rspack.config.js | 25 +++++++ packages/rspack/src/config/adapter.ts | 39 ++++------ packages/rspack/src/config/defaults.ts | 74 ++++--------------- 12 files changed, 215 insertions(+), 209 deletions(-) create mode 100644 packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/child-entry.js create mode 100644 packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/img.png create mode 100644 packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/rspack.config.js diff --git a/crates/rspack_binding_options/src/options/raw_module/mod.rs b/crates/rspack_binding_options/src/options/raw_module/mod.rs index d2d88aecb15..f0dfbf0504d 100644 --- a/crates/rspack_binding_options/src/options/raw_module/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_module/mod.rs @@ -12,11 +12,11 @@ use rspack_core::{ AssetParserDataUrlOptions, AssetParserOptions, AssetResourceGeneratorOptions, CssAutoGeneratorOptions, CssAutoParserOptions, CssGeneratorOptions, CssModuleGeneratorOptions, CssModuleParserOptions, CssParserOptions, DescriptionData, DynamicImportFetchPriority, - DynamicImportMode, ExportPresenceMode, FuncUseCtx, GeneratorOptions, - GeneratorOptionsByModuleType, JavascriptParserOptions, JavascriptParserOrder, - JavascriptParserUrl, ModuleNoParseRule, ModuleNoParseRules, ModuleNoParseTestFn, ModuleOptions, - ModuleRule, ModuleRuleEffect, ModuleRuleEnforce, ModuleRuleUse, ModuleRuleUseLoader, ModuleType, - OverrideStrict, ParserOptions, ParserOptionsByModuleType, + DynamicImportMode, ExportPresenceMode, FuncUseCtx, GeneratorOptions, GeneratorOptionsMap, + JavascriptParserOptions, JavascriptParserOrder, JavascriptParserUrl, ModuleNoParseRule, + ModuleNoParseRules, ModuleNoParseTestFn, ModuleOptions, ModuleRule, ModuleRuleEffect, + ModuleRuleEnforce, ModuleRuleUse, ModuleRuleUseLoader, OverrideStrict, ParserOptions, + ParserOptionsMap, }; use rspack_error::error; use rspack_napi::regexp::{JsRegExp, JsRegExpExt}; @@ -203,14 +203,32 @@ impl From for ParserOptions { .expect("should have an \"asset\" when RawParserOptions.type is \"asset\"") .into(), ), - "javascript" | "javascript/auto" | "javascript/dynamic" | "javascript/esm" => { - Self::Javascript( - value - .javascript - .expect("should have an \"javascript\" when RawParserOptions.type is \"javascript\"") - .into(), - ) - } + "javascript" => Self::Javascript( + value + .javascript + .expect("should have an \"javascript\" when RawParserOptions.type is \"javascript\"") + .into(), + ), + "javascript/auto" => Self::JavascriptAuto( + value + .javascript + .expect("should have an \"javascript\" when RawParserOptions.type is \"javascript/auto\"") + .into(), + ), + "javascript/dynamic" => Self::JavascriptDynamic( + value + .javascript + .expect( + "should have an \"javascript\" when RawParserOptions.type is \"javascript/dynamic\"", + ) + .into(), + ), + "javascript/esm" => Self::JavascriptEsm( + value + .javascript + .expect("should have an \"javascript\" when RawParserOptions.type is \"javascript/esm\"") + .into(), + ), "css" => Self::Css( value .css @@ -805,16 +823,16 @@ impl TryFrom for ModuleOptions { .parser .map(|x| { x.into_iter() - .map(|(k, v)| Ok((ModuleType::from(k.as_str()), v.into()))) - .collect::>() + .map(|(k, v)| Ok((k, v.into()))) + .collect::>() }) .transpose()?, generator: value .generator .map(|x| { x.into_iter() - .map(|(k, v)| Ok((ModuleType::from(k.as_str()), v.into()))) - .collect::>() + .map(|(k, v)| Ok((k, v.into()))) + .collect::>() }) .transpose()?, no_parse: value diff --git a/crates/rspack_core/src/normal_module_factory.rs b/crates/rspack_core/src/normal_module_factory.rs index 26c42c33805..4caff05fadc 100644 --- a/crates/rspack_core/src/normal_module_factory.rs +++ b/crates/rspack_core/src/normal_module_factory.rs @@ -691,25 +691,52 @@ impl NormalModuleFactory { .module .parser .as_ref() - .and_then(|p| p.get(module_type)) - .cloned(); + .and_then(|p| match module_type { + ModuleType::JsAuto | ModuleType::JsDynamic | ModuleType::JsEsm => { + let options = p.get(module_type.as_str()); + let javascript_options = p.get("javascript").cloned(); + // Merge `module.parser.["javascript/xxx"]` with `module.parser.["javascript"]` first + rspack_util::merge_from_optional_with( + javascript_options, + options, + |javascript_options, options| match (javascript_options, options) { + ( + ParserOptions::Javascript(a), + ParserOptions::JavascriptAuto(b) + | ParserOptions::JavascriptDynamic(b) + | ParserOptions::JavascriptEsm(b), + ) => ParserOptions::Javascript(a.merge_from(b)), + _ => unreachable!(), + }, + ) + } + _ => p.get(module_type.as_str()).cloned(), + }); let global_generator = self .options .module .generator .as_ref() - .and_then(|g| g.get(module_type)) - .cloned(); + .and_then(|g| g.get(module_type.as_str()).cloned()); let parser = rspack_util::merge_from_optional_with( global_parser, parser.as_ref(), - |global, local| match (&global, local) { - (ParserOptions::Asset(_), ParserOptions::Asset(_)) - | (ParserOptions::Css(_), ParserOptions::Css(_)) - | (ParserOptions::CssAuto(_), ParserOptions::CssAuto(_)) - | (ParserOptions::CssModule(_), ParserOptions::CssModule(_)) - | (ParserOptions::Javascript(_), ParserOptions::Javascript(_)) => global.merge_from(local), - _ => global, + |global, local| match (global, local) { + (ParserOptions::Asset(a), ParserOptions::Asset(b)) => ParserOptions::Asset(a.merge_from(b)), + (ParserOptions::Css(a), ParserOptions::Css(b)) => ParserOptions::Css(a.merge_from(b)), + (ParserOptions::CssAuto(a), ParserOptions::CssAuto(b)) => { + ParserOptions::CssAuto(a.merge_from(b)) + } + (ParserOptions::CssModule(a), ParserOptions::CssModule(b)) => { + ParserOptions::CssModule(a.merge_from(b)) + } + ( + ParserOptions::Javascript(a), + ParserOptions::JavascriptAuto(b) + | ParserOptions::JavascriptDynamic(b) + | ParserOptions::JavascriptEsm(b), + ) => ParserOptions::Javascript(a.merge_from(b)), + (global, _) => global, }, ); let generator = rspack_util::merge_from_optional_with( diff --git a/crates/rspack_core/src/options/module.rs b/crates/rspack_core/src/options/module.rs index ff54c5e0d4d..a6f01bf5778 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -16,17 +16,17 @@ use rustc_hash::FxHashMap as HashMap; use crate::{Filename, Module, ModuleType, PublicPath, Resolve}; #[derive(Debug)] -pub struct ParserOptionsByModuleType(HashMap); +pub struct ParserOptionsMap(HashMap); -impl FromIterator<(ModuleType, ParserOptions)> for ParserOptionsByModuleType { - fn from_iter>(i: I) -> Self { +impl FromIterator<(String, ParserOptions)> for ParserOptionsMap { + fn from_iter>(i: I) -> Self { Self(HashMap::from_iter(i)) } } -impl ParserOptionsByModuleType { - pub fn get<'a>(&'a self, module_type: &'a ModuleType) -> Option<&'a ParserOptions> { - self.0.get(module_type) +impl ParserOptionsMap { + pub fn get<'a>(&'a self, key: &'a str) -> Option<&'a ParserOptions> { + self.0.get(key) } } @@ -37,6 +37,9 @@ pub enum ParserOptions { CssAuto(CssAutoParserOptions), CssModule(CssModuleParserOptions), Javascript(JavascriptParserOptions), + JavascriptAuto(JavascriptParserOptions), + JavascriptEsm(JavascriptParserOptions), + JavascriptDynamic(JavascriptParserOptions), Unknown, } @@ -57,6 +60,13 @@ impl ParserOptions { get_variant!(get_css_auto, CssAuto, CssAutoParserOptions); get_variant!(get_css_module, CssModule, CssModuleParserOptions); get_variant!(get_javascript, Javascript, JavascriptParserOptions); + get_variant!(get_javascript_auto, JavascriptAuto, JavascriptParserOptions); + get_variant!(get_javascript_esm, JavascriptEsm, JavascriptParserOptions); + get_variant!( + get_javascript_dynamic, + JavascriptDynamic, + JavascriptParserOptions + ); } #[derive(Debug, Clone, Copy, MergeFrom)] @@ -259,17 +269,17 @@ pub struct CssModuleParserOptions { } #[derive(Debug)] -pub struct GeneratorOptionsByModuleType(HashMap); +pub struct GeneratorOptionsMap(HashMap); -impl FromIterator<(ModuleType, GeneratorOptions)> for GeneratorOptionsByModuleType { - fn from_iter>(i: I) -> Self { +impl FromIterator<(String, GeneratorOptions)> for GeneratorOptionsMap { + fn from_iter>(i: I) -> Self { Self(HashMap::from_iter(i)) } } -impl GeneratorOptionsByModuleType { - pub fn get(&self, module_type: &ModuleType) -> Option<&GeneratorOptions> { - self.0.get(module_type) +impl GeneratorOptionsMap { + pub fn get(&self, key: &str) -> Option<&GeneratorOptions> { + self.0.get(key) } } @@ -758,7 +768,7 @@ pub enum ModuleRuleEnforce { #[derive(Debug, Default)] pub struct ModuleOptions { pub rules: Vec, - pub parser: Option, - pub generator: Option, + pub parser: Option, + pub generator: Option, pub no_parse: Option, } diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs index dcda6c5ce19..9e4aa9dd958 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs @@ -198,6 +198,12 @@ static WORKER_FROM_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"^(.+?)(\(\))?\s+from\s+(.+)$").expect("invalid regex")); const WORKER_SPECIFIER_TAG: &str = "_identifier__worker_specifier_tag__"; +const DEFAULT_SYNTAX: [&str; 4] = [ + "Worker", + "SharedWorker", + "navigator.serviceWorker.register()", + "Worker from worker_threads", +]; #[derive(Debug, Clone)] struct WorkerSpecifierData { @@ -214,41 +220,51 @@ impl WorkerPlugin { pattern_syntax: FxHashMap::default(), }; for syntax in syntax_list { - if let Some(syntax) = syntax.strip_prefix('*') - && let Some(first_dot) = syntax.find('.') - && let Some(syntax) = syntax.strip_suffix("()") - { - let pattern = &syntax[0..first_dot]; - let members = &syntax[first_dot + 1..]; - if let Some(value) = this.pattern_syntax.get_mut(pattern) { - value.insert(members.to_string()); - } else { - this.pattern_syntax.insert( - pattern.to_string(), - FxHashSet::from_iter([members.to_string()]), - ); - } - } else if let Some(syntax) = syntax.strip_suffix("()") { - this.call_syntax.insert(syntax.to_string()); - } else if let Some(captures) = WORKER_FROM_REGEX.captures(syntax) { - let ids = &captures[1]; - let is_call = &captures.get(2).is_some(); - let source = &captures[3]; - if *is_call { - this - .from_call_syntax - .insert((ids.to_string(), source.to_string())); - } else { - this - .from_new_syntax - .insert((ids.to_string(), source.to_string())); + if syntax == "..." { + for syntax in DEFAULT_SYNTAX { + this.handle_syntax(syntax); } } else { - this.new_syntax.insert(syntax.to_string()); + this.handle_syntax(syntax); } } this } + + fn handle_syntax(&mut self, syntax: &str) { + if let Some(syntax) = syntax.strip_prefix('*') + && let Some(first_dot) = syntax.find('.') + && let Some(syntax) = syntax.strip_suffix("()") + { + let pattern = &syntax[0..first_dot]; + let members = &syntax[first_dot + 1..]; + if let Some(value) = self.pattern_syntax.get_mut(pattern) { + value.insert(members.to_string()); + } else { + self.pattern_syntax.insert( + pattern.to_string(), + FxHashSet::from_iter([members.to_string()]), + ); + } + } else if let Some(syntax) = syntax.strip_suffix("()") { + self.call_syntax.insert(syntax.to_string()); + } else if let Some(captures) = WORKER_FROM_REGEX.captures(syntax) { + let ids = &captures[1]; + let is_call = &captures.get(2).is_some(); + let source = &captures[3]; + if *is_call { + self + .from_call_syntax + .insert((ids.to_string(), source.to_string())); + } else { + self + .from_new_syntax + .insert((ids.to_string(), source.to_string())); + } + } else { + self.new_syntax.insert(syntax.to_string()); + } + } } impl JavascriptParserPlugin for WorkerPlugin { diff --git a/crates/rspack_util/src/merge.rs b/crates/rspack_util/src/merge.rs index 7ca532b1a09..d0eb4545917 100644 --- a/crates/rspack_util/src/merge.rs +++ b/crates/rspack_util/src/merge.rs @@ -23,10 +23,17 @@ impl MergeFrom for Option { } } -impl MergeFrom for Vec { - fn merge_from(mut self, other: &Self) -> Self { - self.extend(other.iter().cloned()); - self +impl MergeFrom for Vec { + fn merge_from(self, other: &Self) -> Self { + let mut res = Vec::new(); + for item in other { + if item == "..." { + res.extend(self.clone()); + } else { + res.push(item.clone()); + } + } + res } } diff --git a/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap index 08dab610b0f..ea11c5b027b 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap @@ -174,57 +174,6 @@ Object { ], "wrappedContextCritical": false, }, - "javascript/auto": Object { - "dynamicImportMode": "lazy", - "dynamicImportPrefetch": false, - "dynamicImportPreload": false, - "exportsPresence": undefined, - "exprContextCritical": true, - "importExportsPresence": undefined, - "importMeta": true, - "overrideStrict": undefined, - "reexportExportsPresence": undefined, - "strictExportPresence": false, - "url": true, - "worker": Array [ - "...", - ], - "wrappedContextCritical": false, - }, - "javascript/dynamic": Object { - "dynamicImportMode": "lazy", - "dynamicImportPrefetch": false, - "dynamicImportPreload": false, - "exportsPresence": undefined, - "exprContextCritical": true, - "importExportsPresence": undefined, - "importMeta": true, - "overrideStrict": undefined, - "reexportExportsPresence": undefined, - "strictExportPresence": false, - "url": true, - "worker": Array [ - "...", - ], - "wrappedContextCritical": false, - }, - "javascript/esm": Object { - "dynamicImportMode": "lazy", - "dynamicImportPrefetch": false, - "dynamicImportPreload": false, - "exportsPresence": undefined, - "exprContextCritical": true, - "importExportsPresence": undefined, - "importMeta": true, - "overrideStrict": undefined, - "reexportExportsPresence": undefined, - "strictExportPresence": false, - "url": true, - "worker": Array [ - "...", - ], - "wrappedContextCritical": false, - }, }, "rules": Array [], }, diff --git a/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/child-entry.js b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/child-entry.js new file mode 100644 index 00000000000..ddcaf2b30f5 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/child-entry.js @@ -0,0 +1,6 @@ +export const test = (it) => { + it("should not have 'Invalid URL' error with relative URL in child compilation", () => { + const url = new URL("./img.png", import.meta.url); + expect(url.href.endsWith(".png")) + }) +} diff --git a/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/img.png b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/img.png new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/index.js b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/index.js new file mode 100644 index 00000000000..87a5cc2fee8 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/index.js @@ -0,0 +1 @@ +__non_webpack_require__("./__child-main.js").test(it) diff --git a/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/rspack.config.js b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/rspack.config.js new file mode 100644 index 00000000000..50eee9a4efb --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/module/change-parser-in-child-compiler/rspack.config.js @@ -0,0 +1,25 @@ +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + plugins: [ + function (compiler) { + compiler.hooks.make.tapAsync('test', (compilation, callback) => { + const child = compilation.createChildCompiler("test", { + filename: '__child-[name].js', + publicPath: '', + }, [ + new compiler.webpack.library.EnableLibraryPlugin('commonjs'), + new compiler.webpack.EntryPlugin(compilation.options.context, "./child-entry.js", { + name: "main", + library: { + type: "commonjs", + } + }) + ]); + child.options.module.parser.javascript.url = "relative"; + child.runAsChild(callback); + }) + } + ], +}; diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index 7b5ead98906..73143fe602a 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -336,8 +336,8 @@ function getRawModule( ); return { rules, - parser: getRawParserOptionsByModuleType(module.parser), - generator: getRawGeneratorOptionsByModuleType(module.generator), + parser: getRawParserOptionsMap(module.parser), + generator: getRawGeneratorOptionsMap(module.generator), noParse: module.noParse }; } @@ -575,7 +575,7 @@ function getRawRuleSetLogicalConditions( }; } -function getRawParserOptionsByModuleType( +function getRawParserOptionsMap( parser: ParserOptionsByModuleType ): Record { return Object.fromEntries( @@ -585,11 +585,11 @@ function getRawParserOptionsByModuleType( ); } -function getRawGeneratorOptionsByModuleType( - parser: GeneratorOptionsByModuleType +function getRawGeneratorOptionsMap( + generator: GeneratorOptionsByModuleType ): Record { return Object.fromEntries( - Object.entries(parser) + Object.entries(generator) .map(([k, v]) => [k, getRawGeneratorOptions(v, k)]) .filter(([k, v]) => v !== undefined) ); @@ -606,8 +606,10 @@ function getRawParserOptions( }; } if (type === "javascript") { - // Filter this out, since `parser["javascript"]` already merge into `parser["javascript/*"]` in default.ts - return; + return { + type: "javascript", + javascript: getRawJavascriptParserOptions(parser) + }; } if (type === "javascript/auto") { return { @@ -677,25 +679,16 @@ function getRawJavascriptParserOptions( ? "false" : parser.reexportExportsPresence, strictExportPresence: parser.strictExportPresence ?? false, - worker: getRawJavascriptParserOptionsWorker(parser.worker!), + worker: + typeof parser.worker === "boolean" + ? parser.worker + ? ["..."] + : [] + : parser.worker ?? ["..."], overrideStrict: parser.overrideStrict }; } -function getRawJavascriptParserOptionsWorker( - worker: boolean | string[] -): RawJavascriptParserOptions["worker"] { - const DEFAULT_SYNTAX = [ - "Worker", - "SharedWorker", - "navigator.serviceWorker.register()", - "Worker from worker_threads" - ]; - return ( - worker === false ? [] : Array.isArray(worker) ? worker : ["..."] - ).flatMap(item => (item === "..." ? DEFAULT_SYNTAX : item)); -} - function getRawAssetParserOptions( parser: AssetParserOptions ): RawAssetParserOptions { diff --git a/packages/rspack/src/config/defaults.ts b/packages/rspack/src/config/defaults.ts index 7281280cbf7..9af1d263de8 100644 --- a/packages/rspack/src/config/defaults.ts +++ b/packages/rspack/src/config/defaults.ts @@ -234,46 +234,21 @@ const applySnapshotDefaults = ( ) => {}; const applyJavascriptParserOptionsDefaults = ( - parserOptions: JavascriptParserOptions, - fallback?: JavascriptParserOptions + parserOptions: JavascriptParserOptions ) => { - D(parserOptions, "dynamicImportMode", fallback?.dynamicImportMode ?? "lazy"); - D( - parserOptions, - "dynamicImportPrefetch", - fallback?.dynamicImportPrefetch ?? false - ); - D( - parserOptions, - "dynamicImportPreload", - fallback?.dynamicImportPreload ?? false - ); - D(parserOptions, "url", fallback?.url ?? true); - D( - parserOptions, - "exprContextCritical", - fallback?.exprContextCritical ?? true - ); - D( - parserOptions, - "wrappedContextCritical", - fallback?.wrappedContextCritical ?? false - ); - D(parserOptions, "exportsPresence", fallback?.exportsPresence); - D(parserOptions, "importExportsPresence", fallback?.importExportsPresence); - D( - parserOptions, - "reexportExportsPresence", - fallback?.reexportExportsPresence - ); - D( - parserOptions, - "strictExportPresence", - fallback?.strictExportPresence ?? false - ); - D(parserOptions, "worker", fallback?.worker ?? ["..."]); - D(parserOptions, "overrideStrict", fallback?.overrideStrict ?? undefined); - D(parserOptions, "importMeta", fallback?.importMeta ?? true); + D(parserOptions, "dynamicImportMode", "lazy"); + D(parserOptions, "dynamicImportPrefetch", false); + D(parserOptions, "dynamicImportPreload", false); + D(parserOptions, "url", true); + D(parserOptions, "exprContextCritical", true); + D(parserOptions, "wrappedContextCritical", false); + D(parserOptions, "exportsPresence", undefined); + D(parserOptions, "importExportsPresence", undefined); + D(parserOptions, "reexportExportsPresence", undefined); + D(parserOptions, "strictExportPresence", false); + D(parserOptions, "worker", ["..."]); + D(parserOptions, "overrideStrict", undefined); + D(parserOptions, "importMeta", true); }; const applyModuleDefaults = ( @@ -303,27 +278,6 @@ const applyModuleDefaults = ( assertNotNill(module.parser.javascript); applyJavascriptParserOptionsDefaults(module.parser.javascript); - F(module.parser, "javascript/auto", () => ({})); - assertNotNill(module.parser["javascript/auto"]); - applyJavascriptParserOptionsDefaults( - module.parser["javascript/auto"], - module.parser.javascript - ); - - F(module.parser, "javascript/dynamic", () => ({})); - assertNotNill(module.parser["javascript/dynamic"]); - applyJavascriptParserOptionsDefaults( - module.parser["javascript/dynamic"], - module.parser.javascript - ); - - F(module.parser, "javascript/esm", () => ({})); - assertNotNill(module.parser["javascript/esm"]); - applyJavascriptParserOptionsDefaults( - module.parser["javascript/esm"], - module.parser.javascript - ); - if (css) { F(module.parser, "css", () => ({})); assertNotNill(module.parser.css); From ad9109403632439fcf88f4f506da2611d1801537 Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 9 Sep 2024 17:36:34 +0800 Subject: [PATCH 04/51] fix(loader-runner): remove legacy Node compatible code (#7818) fix(loader-runner): no need to compat legacy Node version --- packages/rspack/src/loader-runner/loadLoader.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/rspack/src/loader-runner/loadLoader.ts b/packages/rspack/src/loader-runner/loadLoader.ts index 6fa6e63fb13..28bbe83a66d 100644 --- a/packages/rspack/src/loader-runner/loadLoader.ts +++ b/packages/rspack/src/loader-runner/loadLoader.ts @@ -51,12 +51,7 @@ export default function loadLoader( (e as NodeJS.ErrnoException).code === "EMFILE" ) { const retry = loadLoader.bind(null, loader, callback); - if (typeof setImmediate === "function") { - // node >= 0.9.0 - return void setImmediate(retry); - } - // node < 0.9.0 - return process.nextTick(retry); + return void setImmediate(retry); } return callback(e); } From 2e42f0b7d97ff462657b9862452c74d32e3990f3 Mon Sep 17 00:00:00 2001 From: Gengkun Date: Mon, 9 Sep 2024 17:38:23 +0800 Subject: [PATCH 05/51] fix: improve stats string for match resource with long data url (#7823) --- .../tests/__snapshots__/StatsOutput.test.js.snap | 6 ++++++ .../statsOutputCases/match-resource-data-url/index.js | 2 ++ .../match-resource-data-url/rspack.config.js | 11 +++++++++++ .../rspack/src/stats/DefaultStatsPrinterPlugin.ts | 7 ++++++- 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/index.js create mode 100644 packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/rspack.config.js diff --git a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap index d32cb2fe93e..5310f8bd63c 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap @@ -207,6 +207,12 @@ exports[`statsOutput statsOutput/logging-loader should print correct stats for 1 " `; +exports[`statsOutput statsOutput/match-resource-data-url should print correct stats for 1`] = ` +"runtime modules 677 bytes 3 modules +./index.js 150 bytes [built] [code generated] +./a.js!=!data:javascript,var __looooooooo.. 98 bytes [built] [code generated]" +`; + exports[`statsOutput statsOutput/minify-error should print correct stats for 1`] = ` "ERROR in × JavaScript parsing error: Expected a semicolon ╭─[1:8] diff --git a/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/index.js b/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/index.js new file mode 100644 index 00000000000..4de5f184cf0 --- /dev/null +++ b/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/index.js @@ -0,0 +1,2 @@ +import name from "./a.js!=!data:javascript,var __looooooooooooong_variable_name__ = 1;export default __looooooooooooong_variable_name__ != 1;"; +name; diff --git a/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/rspack.config.js b/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/rspack.config.js new file mode 100644 index 00000000000..6fd77db4fca --- /dev/null +++ b/packages/rspack-test-tools/tests/statsOutputCases/match-resource-data-url/rspack.config.js @@ -0,0 +1,11 @@ +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + entry: "./index.js", + mode: "development", + stats: { + all: false, + modules: true, + } +}; diff --git a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts index ef26e249432..10d6f014b74 100644 --- a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts +++ b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts @@ -45,7 +45,12 @@ const getResourceName = (resource: string) => { }; const getModuleName = (name: string) => { - const [, prefix, resource] = /^(.*!)?([^!]*)$/.exec(name) || []; + const matchResourceMatch = /^([^!]+)!=!/.exec(name); + const n = matchResourceMatch + ? matchResourceMatch[0] + + getResourceName(name.slice(matchResourceMatch[0].length)) + : name; + const [, prefix, resource] = /^(.*!)?([^!]*)$/.exec(n) || []; return [prefix, getResourceName(resource)]; }; From 9ed11a6b57cb97f7e6bad4ab11f15226c32904da Mon Sep 17 00:00:00 2001 From: neverland Date: Mon, 9 Sep 2024 18:06:06 +0800 Subject: [PATCH 06/51] chore(workflow): correct rspack action scope (#7825) --- .github/workflows/release-pull-request.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-pull-request.yml b/.github/workflows/release-pull-request.yml index eb598f41ec2..02176fce894 100644 --- a/.github/workflows/release-pull-request.yml +++ b/.github/workflows/release-pull-request.yml @@ -33,7 +33,8 @@ jobs: uses: ./.github/actions/pnpm-cache - name: Create Release Pull Request - uses: hardfist/rspack-action@rspack + # https://github.com/rspack-contrib/rspack-action + uses: rspack-contrib/rspack-action@rspack with: version: node ./x version ${{inputs.version || 'patch'}} createGithubReleases: false From 8134f86113738d1bf46885bb151ac6e4fae61760 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Mon, 9 Sep 2024 19:36:54 +0800 Subject: [PATCH 07/51] test: ignore snapshot obsolete warnings while using `-t` (#7835) --- package.json | 1 + packages/rspack-test-tools/jest.config.js | 4 ++++ pnpm-lock.yaml | 3 +++ .../test/ignore-snapshot-summary-reporter.cjs | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+) create mode 100644 scripts/test/ignore-snapshot-summary-reporter.cjs diff --git a/package.json b/package.json index 3f8a2b80ffc..26c18339839 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@biomejs/biome": "1.8.0", + "@jest/reporters": "29.7.0", "@microsoft/api-extractor": "^7.43.1", "@microsoft/api-extractor-model": "^7.28.14", "@rspack/cli": "workspace:*", diff --git a/packages/rspack-test-tools/jest.config.js b/packages/rspack-test-tools/jest.config.js index 66a4a599dea..4bf84a0b50a 100644 --- a/packages/rspack-test-tools/jest.config.js +++ b/packages/rspack-test-tools/jest.config.js @@ -8,6 +8,10 @@ const config = { "@rspack/test-tools/setup-expect", "@rspack/test-tools/setup-env" ], + reporters: [ + ["default", null], + "../../scripts/test/ignore-snapshot-summary-reporter.cjs" + ], testTimeout: process.env.CI ? 60000 : 30000, prettierPath: require.resolve("prettier-2"), testMatch: [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a3d0d0084c..f4adc158f54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,6 +16,9 @@ importers: '@biomejs/biome': specifier: 1.8.0 version: 1.8.0 + '@jest/reporters': + specifier: 29.7.0 + version: 29.7.0 '@microsoft/api-extractor': specifier: ^7.43.1 version: 7.43.1(@types/node@20.12.7) diff --git a/scripts/test/ignore-snapshot-summary-reporter.cjs b/scripts/test/ignore-snapshot-summary-reporter.cjs new file mode 100644 index 00000000000..dad3e9a467f --- /dev/null +++ b/scripts/test/ignore-snapshot-summary-reporter.cjs @@ -0,0 +1,21 @@ +const { SummaryReporter } = require("@jest/reporters"); +const chalk = require.cache[require.resolve('@jest/reporters')].require("chalk"); + +if (!process.argv.includes("--verbose") && (process.argv.includes("-t") || process.argv.some(i => i.includes("--testNamePattern")))) { + class IgnoreSnapshotSummaryReporter extends SummaryReporter { + _printSnapshotSummary(snapshots, globalConfig) { + if ( + snapshots.added || + snapshots.filesRemoved || + snapshots.unchecked || + snapshots.unmatched || + snapshots.updated + ) { + this.log(chalk.bold.yellow('Some snapshots are obsoleted, flush with `npm run test -- -u` if necessary.\n')); + } + } + } + module.exports = IgnoreSnapshotSummaryReporter; +} else { + module.exports = SummaryReporter; +} \ No newline at end of file From f195629c20f341a45edc2a1430e2de63e067c2f5 Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Tue, 10 Sep 2024 11:45:08 +0800 Subject: [PATCH 08/51] refactor: remove `ErrorSpan` in dependency trait (#7841) feat: remove ErrorSpan in dependency trait --- crates/rspack_core/src/concatenated_module.rs | 2 +- .../rspack_core/src/context_module_factory.rs | 11 +++++++---- .../src/dependency/dependency_trait.rs | 5 +++-- .../src/dependency/module_dependency.rs | 4 +++- .../src/dependency/compose.rs | 6 +++--- .../rspack_plugin_css/src/dependency/import.rs | 8 ++++---- crates/rspack_plugin_css/src/dependency/url.rs | 10 +++++----- .../src/css_dependency.rs | 18 ++++++++---------- .../src/parser_plugin.rs | 4 ++-- .../common_js_full_require_dependency.rs | 6 +++--- .../commonjs/common_js_require_dependency.rs | 9 +++------ .../commonjs/require_resolve_dependency.rs | 8 ++++---- .../common_js_require_context_dependency.rs | 6 +++--- .../context/import_context_dependency.rs | 6 +++--- .../context/import_meta_context_dependency.rs | 6 +++--- .../context/require_context_dependency.rs | 6 +++--- ...ony_export_imported_specifier_dependency.rs | 10 +++++----- .../esm/harmony_import_dependency.rs | 6 +++--- .../esm/harmony_import_specifier_dependency.rs | 7 ++----- .../src/dependency/esm/import_dependency.rs | 8 ++++---- .../dependency/esm/import_eager_dependency.rs | 6 +++--- .../src/dependency/esm/provide_dependency.rs | 6 +----- .../dependency/hmr/import_meta_hot_accept.rs | 8 ++++---- .../dependency/hmr/import_meta_hot_decline.rs | 8 ++++---- .../src/dependency/hmr/module_hot_accept.rs | 8 ++++---- .../src/dependency/hmr/module_hot_decline.rs | 8 ++++---- .../src/dependency/url/mod.rs | 6 +++--- .../src/dependency/worker/mod.rs | 10 +++++----- .../src/modern_module/dependency.rs | 6 +++--- 29 files changed, 103 insertions(+), 109 deletions(-) diff --git a/crates/rspack_core/src/concatenated_module.rs b/crates/rspack_core/src/concatenated_module.rs index a785a8c1a2f..314ad7ec98b 100644 --- a/crates/rspack_core/src/concatenated_module.rs +++ b/crates/rspack_core/src/concatenated_module.rs @@ -1607,7 +1607,7 @@ impl ConcatenatedModule { source_order: dep .source_order() .expect("source order should not be empty"), - range_start: dep.span().map(|span| span.start), + range_start: dep.range().map(|range| range.start), }) }) .collect::>(); diff --git a/crates/rspack_core/src/context_module_factory.rs b/crates/rspack_core/src/context_module_factory.rs index 2b6dc451834..c6a02db9377 100644 --- a/crates/rspack_core/src/context_module_factory.rs +++ b/crates/rspack_core/src/context_module_factory.rs @@ -7,9 +7,10 @@ use rspack_regex::RspackRegex; use tracing::instrument; use crate::{ - resolve, ContextModule, ContextModuleOptions, DependencyCategory, ModuleExt, ModuleFactory, - ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, RawModule, ResolveArgs, - ResolveOptionsWithDependencyType, ResolveResult, Resolver, ResolverFactory, SharedPluginDriver, + resolve, ContextModule, ContextModuleOptions, DependencyCategory, ErrorSpan, ModuleExt, + ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, RawModule, + ResolveArgs, ResolveOptionsWithDependencyType, ResolveResult, Resolver, ResolverFactory, + SharedPluginDriver, }; #[derive(Clone)] @@ -221,7 +222,9 @@ impl ContextModuleFactory { specifier, dependency_type: dependency.dependency_type(), dependency_category: dependency.category(), - span: dependency.span(), + span: dependency + .range() + .map(|range| ErrorSpan::new(range.start, range.end)), resolve_options: data.resolve_options.clone(), resolve_to_context: true, optional: dependency.get_optional(), diff --git a/crates/rspack_core/src/dependency/dependency_trait.rs b/crates/rspack_core/src/dependency/dependency_trait.rs index 3462fe9282e..7b9eb202c30 100644 --- a/crates/rspack_core/src/dependency/dependency_trait.rs +++ b/crates/rspack_core/src/dependency/dependency_trait.rs @@ -9,6 +9,7 @@ use swc_core::ecma::atoms::Atom; use super::dependency_template::AsDependencyTemplate; use super::module_dependency::*; use super::ExportsSpec; +use super::RealDependencyLocation; use super::{DependencyCategory, DependencyId, DependencyType}; use crate::create_exports_object_referenced; use crate::AsContextDependency; @@ -16,7 +17,7 @@ use crate::ExtendedReferencedExport; use crate::ImportAttributes; use crate::ModuleLayer; use crate::RuntimeSpec; -use crate::{ConnectionState, Context, ErrorSpan, ModuleGraph, UsedByExports}; +use crate::{ConnectionState, Context, ModuleGraph, UsedByExports}; #[derive(Debug, Clone, Copy)] pub enum AffectType { @@ -77,7 +78,7 @@ pub trait Dependency: None } - fn span(&self) -> Option { + fn range(&self) -> Option<&RealDependencyLocation> { None } diff --git a/crates/rspack_core/src/dependency/module_dependency.rs b/crates/rspack_core/src/dependency/module_dependency.rs index 7b1b8655930..ce5c49003d4 100644 --- a/crates/rspack_core/src/dependency/module_dependency.rs +++ b/crates/rspack_core/src/dependency/module_dependency.rs @@ -15,7 +15,9 @@ pub trait ModuleDependency: Dependency { /// This is only intended used to display better diagnostics. /// So it might not be precise as it is using [crate::Dependency::span] as the default value. fn source_span(&self) -> Option { - self.span() + self + .range() + .map(|range| ErrorSpan::new(range.start, range.end)) } // TODO: move to ModuleGraphConnection diff --git a/crates/rspack_plugin_css/src/dependency/compose.rs b/crates/rspack_plugin_css/src/dependency/compose.rs index 944a7c1a53d..4ab5d795ddf 100644 --- a/crates/rspack_plugin_css/src/dependency/compose.rs +++ b/crates/rspack_plugin_css/src/dependency/compose.rs @@ -1,6 +1,6 @@ use rspack_core::{ AsContextDependency, AsDependencyTemplate, Dependency, DependencyCategory, DependencyId, - DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, + DependencyType, ModuleDependency, RealDependencyLocation, }; #[derive(Debug, Clone)] @@ -33,8 +33,8 @@ impl Dependency for CssComposeDependency { &DependencyType::CssCompose } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_css/src/dependency/import.rs b/crates/rspack_plugin_css/src/dependency/import.rs index 981de9ea34b..6982c685d44 100644 --- a/crates/rspack_plugin_css/src/dependency/import.rs +++ b/crates/rspack_plugin_css/src/dependency/import.rs @@ -1,7 +1,7 @@ use rspack_core::{ AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, - RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ModuleDependency, RealDependencyLocation, RuntimeSpec, + TemplateContext, TemplateReplaceSource, }; #[derive(Debug, Clone)] @@ -34,8 +34,8 @@ impl Dependency for CssImportDependency { &DependencyType::CssImport } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_css/src/dependency/url.rs b/crates/rspack_plugin_css/src/dependency/url.rs index a7d6688a47a..5e2d3f5e0f6 100644 --- a/crates/rspack_plugin_css/src/dependency/url.rs +++ b/crates/rspack_plugin_css/src/dependency/url.rs @@ -1,8 +1,8 @@ use rspack_core::{ AsContextDependency, CodeGenerationDataFilename, CodeGenerationDataUrl, Compilation, Dependency, - DependencyCategory, DependencyId, DependencyTemplate, DependencyType, ErrorSpan, - ModuleDependency, ModuleIdentifier, PublicPath, RealDependencyLocation, RuntimeSpec, - TemplateContext, TemplateReplaceSource, + DependencyCategory, DependencyId, DependencyTemplate, DependencyType, ModuleDependency, + ModuleIdentifier, PublicPath, RealDependencyLocation, RuntimeSpec, TemplateContext, + TemplateReplaceSource, }; use crate::utils::{css_escape_string, AUTO_PUBLIC_PATH_PLACEHOLDER}; @@ -64,8 +64,8 @@ impl Dependency for CssUrlDependency { &DependencyType::CssUrl } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_extract_css/src/css_dependency.rs b/crates/rspack_plugin_extract_css/src/css_dependency.rs index f3bddd72b31..f9d5d9af060 100644 --- a/crates/rspack_plugin_extract_css/src/css_dependency.rs +++ b/crates/rspack_plugin_extract_css/src/css_dependency.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use rspack_collections::IdentifierSet; use rspack_core::{ AffectType, AsContextDependency, AsDependencyTemplate, ConnectionState, Dependency, - DependencyCategory, DependencyId, ModuleDependency, ModuleGraph, + DependencyCategory, DependencyId, ModuleDependency, ModuleGraph, RealDependencyLocation, }; use rustc_hash::FxHashSet; @@ -24,8 +24,9 @@ pub struct CssDependency { pub(crate) identifier_index: u32, // determine module's postOrderIndex - pub(crate) order_index: u32, - + // @TODO(shulaoda) Does this have any additional side effects? + // pub(crate) order_index: u32, + range: RealDependencyLocation, resource_identifier: String, pub(crate) cacheable: bool, pub(crate) file_dependencies: FxHashSet, @@ -45,7 +46,7 @@ impl CssDependency { supports: Option, source_map: Option, identifier_index: u32, - order_index: u32, + range: RealDependencyLocation, cacheable: bool, file_dependencies: FxHashSet, context_dependencies: FxHashSet, @@ -63,7 +64,7 @@ impl CssDependency { supports, source_map, identifier_index, - order_index, + range, resource_identifier, cacheable, file_dependencies, @@ -107,11 +108,8 @@ impl Dependency for CssDependency { // it can keep the right order, but Rspack uses HashSet, // when determining the postOrderIndex, Rspack uses // dependency span to set correct order - fn span(&self) -> Option { - Some(rspack_core::ErrorSpan { - start: self.order_index, - end: self.order_index + 1, - }) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_layer(&self) -> Option<&rspack_core::ModuleLayer> { diff --git a/crates/rspack_plugin_extract_css/src/parser_plugin.rs b/crates/rspack_plugin_extract_css/src/parser_plugin.rs index 8d6e5e1c568..488ed607927 100644 --- a/crates/rspack_plugin_extract_css/src/parser_plugin.rs +++ b/crates/rspack_plugin_extract_css/src/parser_plugin.rs @@ -1,4 +1,4 @@ -use rspack_core::BoxDependency; +use rspack_core::{BoxDependency, RealDependencyLocation}; use rspack_plugin_javascript::{visitors::JavascriptParser, JavascriptParserPlugin}; use rspack_util::fx_hash::FxDashMap; use serde::Deserialize; @@ -60,7 +60,7 @@ impl JavascriptParserPlugin for PluginCssExtractParserPlugin { supports.clone(), source_map.clone(), *identifier_index, - index as u32, + RealDependencyLocation::new(index as u32, (index + 1) as u32), parser.build_info.cacheable, parser.build_info.file_dependencies.clone(), parser.build_info.context_dependencies.clone(), diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_full_require_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_full_require_dependency.rs index e9b9e254f2d..6da0669be68 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_full_require_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_full_require_dependency.rs @@ -5,7 +5,7 @@ use rspack_core::{ }; use rspack_core::{AsContextDependency, Dependency, DependencyCategory}; use rspack_core::{DependencyId, DependencyTemplate}; -use rspack_core::{DependencyType, ErrorSpan, ModuleDependency}; +use rspack_core::{DependencyType, ModuleDependency}; use rspack_core::{TemplateContext, TemplateReplaceSource}; use swc_core::atoms::Atom; @@ -58,8 +58,8 @@ impl Dependency for CommonJsFullRequireDependency { Some(self.range.to_string()) } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_referenced_exports( diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_require_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_require_dependency.rs index 5e8ef895b7a..9e13231ce6f 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_require_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/common_js_require_dependency.rs @@ -1,7 +1,7 @@ use rspack_core::{module_id, Compilation, RealDependencyLocation, RuntimeSpec}; use rspack_core::{AsContextDependency, Dependency, DependencyCategory}; use rspack_core::{DependencyId, DependencyTemplate}; -use rspack_core::{DependencyType, ErrorSpan, ModuleDependency}; +use rspack_core::{DependencyType, ModuleDependency}; use rspack_core::{TemplateContext, TemplateReplaceSource}; #[derive(Debug, Clone)] @@ -47,11 +47,8 @@ impl Dependency for CommonJsRequireDependency { &DependencyType::CjsRequire } - fn span(&self) -> Option { - self - .range - .clone() - .map(|range| ErrorSpan::new(range.start, range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + self.range.as_ref() } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_dependency.rs index 86ff2343cd6..e1fc470a77f 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_dependency.rs @@ -1,7 +1,7 @@ use rspack_core::{ module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ExtendedReferencedExport, ModuleDependency, - ModuleGraph, RealDependencyLocation, RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ExtendedReferencedExport, ModuleDependency, ModuleGraph, + RealDependencyLocation, RuntimeSpec, TemplateContext, TemplateReplaceSource, }; #[derive(Debug, Clone)] @@ -38,8 +38,8 @@ impl Dependency for RequireResolveDependency { &DependencyType::RequireResolve } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_referenced_exports( diff --git a/crates/rspack_plugin_javascript/src/dependency/context/common_js_require_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/common_js_require_context_dependency.rs index 1638ce7d4ff..35fb428a146 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/common_js_require_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/common_js_require_context_dependency.rs @@ -3,7 +3,7 @@ use rspack_core::{ }; use rspack_core::{ContextOptions, Dependency, TemplateReplaceSource}; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; -use rspack_core::{DependencyType, ErrorSpan, TemplateContext}; +use rspack_core::{DependencyType, TemplateContext}; use super::{ context_dependency_template_as_require_call, create_resource_identifier_for_context_dependency, @@ -51,8 +51,8 @@ impl Dependency for CommonJsRequireContextDependency { &DependencyType::CommonJSRequireContext } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs index da5f52c38ae..dbd36ab9097 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/import_context_dependency.rs @@ -3,7 +3,7 @@ use rspack_core::{ }; use rspack_core::{ContextOptions, Dependency, TemplateReplaceSource}; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; -use rspack_core::{DependencyType, ErrorSpan, TemplateContext}; +use rspack_core::{DependencyType, TemplateContext}; use super::{ context_dependency_template_as_require_call, create_resource_identifier_for_context_dependency, @@ -51,8 +51,8 @@ impl Dependency for ImportContextDependency { &DependencyType::ImportContext } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/context/import_meta_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/import_meta_context_dependency.rs index 5d98a7b1a6a..77c87089959 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/import_meta_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/import_meta_context_dependency.rs @@ -3,7 +3,7 @@ use rspack_core::{ RuntimeSpec, }; use rspack_core::{ContextOptions, Dependency, DependencyCategory, DependencyId}; -use rspack_core::{DependencyTemplate, DependencyType, ErrorSpan}; +use rspack_core::{DependencyTemplate, DependencyType}; use rspack_core::{TemplateContext, TemplateReplaceSource}; use super::create_resource_identifier_for_context_dependency; @@ -43,8 +43,8 @@ impl Dependency for ImportMetaContextDependency { &DependencyType::ImportMetaContext } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/context/require_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/require_context_dependency.rs index 18db2189956..bbfa8d557a9 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/require_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/require_context_dependency.rs @@ -3,7 +3,7 @@ use rspack_core::{ RuntimeSpec, }; use rspack_core::{ContextOptions, Dependency, DependencyCategory, DependencyId}; -use rspack_core::{DependencyTemplate, DependencyType, ErrorSpan}; +use rspack_core::{DependencyTemplate, DependencyType}; use rspack_core::{TemplateContext, TemplateReplaceSource}; use super::create_resource_identifier_for_context_dependency; @@ -43,8 +43,8 @@ impl Dependency for RequireContextDependency { &DependencyType::RequireContext } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs index 6bb5e277c9c..c05e3a8c341 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_export_imported_specifier_dependency.rs @@ -8,8 +8,8 @@ use rspack_core::{ process_export_info, property_access, property_name, string_of_used_name, AsContextDependency, Compilation, ConditionalInitFragment, ConnectionState, Dependency, DependencyCategory, DependencyCondition, DependencyConditionFn, DependencyId, DependencyTemplate, DependencyType, - ErrorSpan, ExportInfo, ExportInfoProvided, ExportNameOrSpec, ExportPresenceMode, ExportSpec, - ExportsInfo, ExportsOfExportsSpec, ExportsSpec, ExportsType, ExtendedReferencedExport, + ExportInfo, ExportInfoProvided, ExportNameOrSpec, ExportPresenceMode, ExportSpec, ExportsInfo, + ExportsOfExportsSpec, ExportsSpec, ExportsType, ExtendedReferencedExport, HarmonyExportInitFragment, ImportAttributes, InitFragmentExt, InitFragmentKey, InitFragmentStage, JavascriptParserOptions, ModuleDependency, ModuleGraph, ModuleIdentifier, NormalInitFragment, RealDependencyLocation, RuntimeCondition, RuntimeGlobals, RuntimeSpec, Template, TemplateContext, @@ -866,7 +866,7 @@ impl HarmonyExportImportedSpecifierDependency { let parent_module_identifier = module_graph .get_parent_module(&self.id) .expect("should have parent module for dependency"); - let mut diagnostic = if let Some(span) = self.span() + let mut diagnostic = if let Some(span) = self.range() && let Some(parent_module) = module_graph.module_by_identifier(parent_module_identifier) && let Some(source) = parent_module.original_source().map(|s| s.source()) { @@ -1054,8 +1054,8 @@ impl Dependency for HarmonyExportImportedSpecifierDependency { Some(self.range.to_string()) } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn category(&self) -> &DependencyCategory { diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_dependency.rs index d835a787390..781e98593a9 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/harmony_import_dependency.rs @@ -230,7 +230,7 @@ pub fn harmony_import_dependency_get_linking_error( } else { (Severity::Warning, "HarmonyLinkingWarning") }; - let mut diagnostic = if let Some(span) = module_dependency.span() + let mut diagnostic = if let Some(span) = module_dependency.range() && let Some(source) = parent_module.original_source().map(|s| s.source()) { Diagnostic::from( @@ -380,8 +380,8 @@ impl Dependency for HarmonyImportSideEffectDependency { Some(self.range.to_string()) } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn source_order(&self) -> Option { 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 a22e4c194bf..71405c4ba34 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 @@ -228,11 +228,8 @@ impl Dependency for HarmonyImportSpecifierDependency { Some(self.range.to_string()) } - fn span(&self) -> Option { - Some(rspack_core::ErrorSpan::new( - self.range.start, - self.range.end, - )) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn source_order(&self) -> Option { diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs index 11b7c403727..f994de56db2 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/import_dependency.rs @@ -1,7 +1,7 @@ use rspack_core::{ create_exports_object_referenced, module_namespace_promise, Compilation, DependencyType, - ErrorSpan, ExportsType, ExtendedReferencedExport, ImportAttributes, ModuleGraph, - RealDependencyLocation, ReferencedExport, RuntimeSpec, + ExportsType, ExtendedReferencedExport, ImportAttributes, ModuleGraph, RealDependencyLocation, + ReferencedExport, RuntimeSpec, }; use rspack_core::{AsContextDependency, Dependency}; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; @@ -103,8 +103,8 @@ impl Dependency for ImportDependency { self.attributes.as_ref() } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_referenced_exports( diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs index 41c62f20fb4..b996032592b 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/import_eager_dependency.rs @@ -1,6 +1,6 @@ use rspack_core::{ module_namespace_promise, AsContextDependency, Compilation, Dependency, DependencyCategory, - DependencyId, DependencyTemplate, DependencyType, ErrorSpan, ImportAttributes, ModuleDependency, + DependencyId, DependencyTemplate, DependencyType, ImportAttributes, ModuleDependency, RealDependencyLocation, RuntimeSpec, TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -61,8 +61,8 @@ impl Dependency for ImportEagerDependency { self.attributes.as_ref() } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_referenced_exports( diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs index 8055b1792b9..cd225f8dd2b 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs @@ -1,11 +1,11 @@ use itertools::Itertools; +use rspack_core::DependencyType; use rspack_core::{ create_exports_object_referenced, module_raw, Compilation, ExtendedReferencedExport, ModuleGraph, NormalInitFragment, RuntimeSpec, UsedName, }; use rspack_core::{AsContextDependency, Dependency, InitFragmentKey, InitFragmentStage}; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; -use rspack_core::{DependencyType, ErrorSpan}; use rspack_core::{ModuleDependency, TemplateContext, TemplateReplaceSource}; use rspack_util::ext::DynHash; use swc_core::atoms::Atom; @@ -46,10 +46,6 @@ impl Dependency for ProvideDependency { &DependencyType::Provided } - fn span(&self) -> Option { - None - } - fn get_referenced_exports( &self, _module_graph: &ModuleGraph, diff --git a/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_accept.rs b/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_accept.rs index f40534c64d8..601c7ffe1f4 100644 --- a/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_accept.rs +++ b/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_accept.rs @@ -1,7 +1,7 @@ use rspack_core::{ module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, - RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ModuleDependency, RealDependencyLocation, RuntimeSpec, + TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -35,8 +35,8 @@ impl Dependency for ImportMetaHotAcceptDependency { &DependencyType::ImportMetaHotAccept } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_decline.rs b/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_decline.rs index b6aa4d4cffb..7fd044ba3c0 100644 --- a/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_decline.rs +++ b/crates/rspack_plugin_javascript/src/dependency/hmr/import_meta_hot_decline.rs @@ -1,7 +1,7 @@ use rspack_core::{ module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, - RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ModuleDependency, RealDependencyLocation, RuntimeSpec, + TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -35,8 +35,8 @@ impl Dependency for ImportMetaHotDeclineDependency { &DependencyType::ImportMetaHotDecline } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_accept.rs b/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_accept.rs index ebc4af526d9..4f4400349a3 100644 --- a/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_accept.rs +++ b/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_accept.rs @@ -1,7 +1,7 @@ use rspack_core::{ module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, - RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ModuleDependency, RealDependencyLocation, RuntimeSpec, + TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -35,8 +35,8 @@ impl Dependency for ModuleHotAcceptDependency { &DependencyType::ModuleHotAccept } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_decline.rs b/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_decline.rs index f058c1d6fd9..9228da1bc92 100644 --- a/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_decline.rs +++ b/crates/rspack_plugin_javascript/src/dependency/hmr/module_hot_decline.rs @@ -1,7 +1,7 @@ use rspack_core::{ module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, - DependencyTemplate, DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, - RuntimeSpec, TemplateContext, TemplateReplaceSource, + DependencyTemplate, DependencyType, ModuleDependency, RealDependencyLocation, RuntimeSpec, + TemplateContext, TemplateReplaceSource, }; use swc_core::ecma::atoms::Atom; @@ -35,8 +35,8 @@ impl Dependency for ModuleHotDeclineDependency { &DependencyType::ModuleHotDecline } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/url/mod.rs b/crates/rspack_plugin_javascript/src/dependency/url/mod.rs index 16dad76cf42..1f1254c2616 100644 --- a/crates/rspack_plugin_javascript/src/dependency/url/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/url/mod.rs @@ -1,7 +1,7 @@ use rspack_core::{ get_dependency_used_by_exports_condition, module_id, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyCondition, DependencyId, DependencyTemplate, - DependencyType, ErrorSpan, ModuleDependency, RealDependencyLocation, RuntimeGlobals, RuntimeSpec, + DependencyType, ModuleDependency, RealDependencyLocation, RuntimeGlobals, RuntimeSpec, TemplateContext, TemplateReplaceSource, UsedByExports, }; use swc_core::ecma::atoms::Atom; @@ -47,8 +47,8 @@ impl Dependency for URLDependency { &DependencyType::NewUrl } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { diff --git a/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs b/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs index 879ca9221ba..f1b2a2bddd1 100644 --- a/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs @@ -1,8 +1,8 @@ use rspack_core::{ get_chunk_from_ukey, AsContextDependency, Compilation, Dependency, DependencyCategory, - DependencyId, DependencyTemplate, DependencyType, ErrorSpan, ExtendedReferencedExport, - ModuleDependency, ModuleGraph, RealDependencyLocation, RuntimeGlobals, RuntimeSpec, - TemplateContext, TemplateReplaceSource, + DependencyId, DependencyTemplate, DependencyType, ExtendedReferencedExport, ModuleDependency, + ModuleGraph, RealDependencyLocation, RuntimeGlobals, RuntimeSpec, TemplateContext, + TemplateReplaceSource, }; use rspack_util::ext::DynHash; @@ -45,8 +45,8 @@ impl Dependency for WorkerDependency { &DependencyType::NewWorker } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn get_referenced_exports( diff --git a/crates/rspack_plugin_library/src/modern_module/dependency.rs b/crates/rspack_plugin_library/src/modern_module/dependency.rs index 90dd8bf27d7..6d5c7019bd8 100644 --- a/crates/rspack_plugin_library/src/modern_module/dependency.rs +++ b/crates/rspack_plugin_library/src/modern_module/dependency.rs @@ -1,6 +1,6 @@ use rspack_core::{AsContextDependency, Dependency}; use rspack_core::{ - Compilation, DependencyType, ErrorSpan, ExternalRequest, ExternalType, ImportAttributes, + Compilation, DependencyType, ExternalRequest, ExternalType, ImportAttributes, RealDependencyLocation, RuntimeSpec, }; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; @@ -62,8 +62,8 @@ impl Dependency for ModernModuleImportDependency { self.attributes.as_ref() } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> rspack_core::AffectType { From 0969538afd86f58c6cc64eeb855c8985bdaf2429 Mon Sep 17 00:00:00 2001 From: inottn Date: Tue, 10 Sep 2024 11:56:43 +0800 Subject: [PATCH 09/51] feat: support context for resolve and resolveWeak (#7832) --- .../src/dependency/commonjs/mod.rs | 2 + .../require_resolve_header_dependency.rs | 63 ++++++++++ .../src/dependency/context/mod.rs | 48 +++++++- .../require_resolve_context_dependency.rs | 106 ++++++++++++++++ .../common_js_imports_parse_plugin.rs | 114 ++++++++++++++++-- .../__snapshots__/StatsOutput.test.js.snap | 8 +- .../chunks/weak-dependencies-context/index.js | 4 +- .../weak-dependencies-context/test.filter.js | 4 - .../cases/parsing/complex-require/cjs.js | 24 ++-- .../resolve-weak-context/test.filter.js | 4 - 10 files changed, 337 insertions(+), 40 deletions(-) create mode 100644 crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_header_dependency.rs create mode 100644 crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs delete mode 100644 tests/webpack-test/cases/chunks/weak-dependencies-context/test.filter.js delete mode 100644 tests/webpack-test/cases/parsing/resolve-weak-context/test.filter.js diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs index 7cff0c3a2fe..adb6cd6cac6 100644 --- a/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/mod.rs @@ -6,6 +6,7 @@ mod common_js_self_reference_dependency; mod module_decorator_dependency; mod require_header_dependency; mod require_resolve_dependency; +mod require_resolve_header_dependency; pub use common_js_export_require_dependency::CommonJsExportRequireDependency; pub use common_js_exports_dependency::CommonJsExportsDependency; @@ -16,3 +17,4 @@ pub use common_js_self_reference_dependency::CommonJsSelfReferenceDependency; pub use module_decorator_dependency::ModuleDecoratorDependency; pub use require_header_dependency::RequireHeaderDependency; pub use require_resolve_dependency::RequireResolveDependency; +pub use require_resolve_header_dependency::RequireResolveHeaderDependency; diff --git a/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_header_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_header_dependency.rs new file mode 100644 index 00000000000..83b97ce3a42 --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/commonjs/require_resolve_header_dependency.rs @@ -0,0 +1,63 @@ +use rspack_core::{ + AffectType, AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyId, + DependencyTemplate, RealDependencyLocation, RuntimeSpec, TemplateContext, TemplateReplaceSource, +}; + +#[derive(Debug, Clone)] +pub struct RequireResolveHeaderDependency { + id: DependencyId, + range: RealDependencyLocation, +} + +impl RequireResolveHeaderDependency { + pub fn new(range: RealDependencyLocation) -> Self { + Self { + id: DependencyId::new(), + range, + } + } +} + +impl Dependency for RequireResolveHeaderDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn loc(&self) -> Option { + Some(self.range.to_string()) + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::False + } +} + +impl AsModuleDependency for RequireResolveHeaderDependency {} +impl AsContextDependency for RequireResolveHeaderDependency {} + +impl DependencyTemplate for RequireResolveHeaderDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + _code_generatable_context: &mut TemplateContext, + ) { + source.replace( + self.range.start, + self.range.end, + "/*require.resolve*/", + None, + ); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} diff --git a/crates/rspack_plugin_javascript/src/dependency/context/mod.rs b/crates/rspack_plugin_javascript/src/dependency/context/mod.rs index c9828b39376..3bffc502332 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/mod.rs @@ -2,13 +2,16 @@ mod common_js_require_context_dependency; mod import_context_dependency; mod import_meta_context_dependency; mod require_context_dependency; +mod require_resolve_context_dependency; pub use common_js_require_context_dependency::CommonJsRequireContextDependency; pub use import_context_dependency::ImportContextDependency; pub use import_meta_context_dependency::ImportMetaContextDependency; pub use require_context_dependency::RequireContextDependency; +pub use require_resolve_context_dependency::RequireResolveContextDependency; use rspack_core::{ - module_raw, ContextDependency, ContextOptions, TemplateContext, TemplateReplaceSource, + module_raw, ContextDependency, ContextMode, ContextOptions, RealDependencyLocation, + TemplateContext, TemplateReplaceSource, }; fn create_resource_identifier_for_context_dependency( @@ -72,3 +75,46 @@ fn context_dependency_template_as_require_call( } source.replace(callee_start, callee_end, &expr, None); } + +fn context_dependency_template_as_id( + dep: &dyn ContextDependency, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + range: &RealDependencyLocation, +) { + let TemplateContext { + compilation, + runtime_requirements, + .. + } = code_generatable_context; + let id = dep.id(); + + let expr = module_raw( + compilation, + runtime_requirements, + id, + dep.request(), + dep.options().mode == ContextMode::Weak, + ); + + if compilation + .get_module_graph() + .module_graph_module_by_dependency_id(id) + .is_none() + { + source.replace(range.start, range.end, &expr, None); + return; + } + + for (content, start, end) in &dep.options().replaces { + source.replace(*start, *end - 1, content, None); + } + + source.replace( + range.start, + range.start, + &format!("{}.resolve(", &expr), + None, + ); + source.replace(range.end, range.end, ")", None); +} diff --git a/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs new file mode 100644 index 00000000000..eb9ea08a61b --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs @@ -0,0 +1,106 @@ +use rspack_core::{ + AffectType, AsModuleDependency, Compilation, ContextDependency, ContextOptions, + ContextTypePrefix, Dependency, DependencyCategory, DependencyId, DependencyTemplate, + DependencyType, ErrorSpan, RealDependencyLocation, RuntimeSpec, TemplateContext, + TemplateReplaceSource, +}; + +use super::{context_dependency_template_as_id, create_resource_identifier_for_context_dependency}; + +#[derive(Debug, Clone)] +pub struct RequireResolveContextDependency { + id: DependencyId, + options: ContextOptions, + range: RealDependencyLocation, + resource_identifier: String, + optional: bool, +} + +impl RequireResolveContextDependency { + pub fn new(options: ContextOptions, range: RealDependencyLocation, optional: bool) -> Self { + let resource_identifier = create_resource_identifier_for_context_dependency(None, &options); + Self { + id: DependencyId::new(), + options, + range, + resource_identifier, + optional, + } + } +} + +impl Dependency for RequireResolveContextDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::CommonJS + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::RequireContext + } + + fn span(&self) -> Option { + Some(ErrorSpan::new(self.range.start, self.range.end)) + } + + fn could_affect_referencing_module(&self) -> AffectType { + AffectType::True + } +} + +impl ContextDependency for RequireResolveContextDependency { + fn request(&self) -> &str { + &self.options.request + } + + fn options(&self) -> &ContextOptions { + &self.options + } + + fn get_context(&self) -> Option<&str> { + None + } + + fn resource_identifier(&self) -> &str { + &self.resource_identifier + } + + fn set_request(&mut self, request: String) { + self.options.request = request; + } + + fn get_optional(&self) -> bool { + self.optional + } + + fn type_prefix(&self) -> ContextTypePrefix { + ContextTypePrefix::Normal + } +} + +impl DependencyTemplate for RequireResolveContextDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + context_dependency_template_as_id(self, source, code_generatable_context, &self.range); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for RequireResolveContextDependency {} diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs index 9afe27ba55f..308a41e9d73 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/common_js_imports_parse_plugin.rs @@ -4,12 +4,14 @@ use rspack_core::{ use rspack_core::{ContextNameSpaceObject, ContextOptions}; use rspack_error::Severity; use swc_core::common::{Span, Spanned}; -use swc_core::ecma::ast::{CallExpr, Expr, Ident, Lit, MemberExpr, UnaryExpr}; +use swc_core::ecma::ast::{CallExpr, Expr, Ident, MemberExpr, UnaryExpr}; use super::JavascriptParserPlugin; -use crate::dependency::RequireHeaderDependency; -use crate::dependency::{CommonJsFullRequireDependency, CommonJsRequireContextDependency}; -use crate::dependency::{CommonJsRequireDependency, RequireResolveDependency}; +use crate::dependency::{ + CommonJsFullRequireDependency, CommonJsRequireContextDependency, CommonJsRequireDependency, + RequireHeaderDependency, RequireResolveContextDependency, RequireResolveDependency, + RequireResolveHeaderDependency, +}; use crate::utils::eval::{self, BasicEvaluatedExpression}; use crate::visitors::{ context_reg_exp, create_context_dependency, create_traceable_error, expr_matcher, expr_name, @@ -47,23 +49,109 @@ fn create_commonjs_require_context_dependency( CommonJsRequireContextDependency::new(options, span.into(), (start, end), parser.in_try) } +fn create_require_resolve_context_dependency( + parser: &mut JavascriptParser, + param: &BasicEvaluatedExpression, + expr: &Expr, + range: RealDependencyLocation, + weak: bool, +) -> RequireResolveContextDependency { + let start = range.start; + let end = range.end; + let result = create_context_dependency(param, expr, parser); + let options = ContextOptions { + mode: if weak { + ContextMode::Weak + } else { + ContextMode::Sync + }, + recursive: true, + reg_exp: context_reg_exp(&result.reg, "", None, parser), + include: None, + exclude: None, + category: DependencyCategory::CommonJS, + request: format!("{}{}{}", result.context, result.query, result.fragment), + context: result.context, + namespace_object: ContextNameSpaceObject::Unset, + group_options: None, + replaces: result.replaces, + start, + end, + referenced_exports: None, + attributes: None, + }; + RequireResolveContextDependency::new(options, range, parser.in_try) +} + pub struct CommonJsImportsParserPlugin; impl CommonJsImportsParserPlugin { - fn add_require_resolve(&self, parser: &mut JavascriptParser, node: &CallExpr, weak: bool) { - if !node.args.is_empty() - && let Some(Lit::Str(str)) = node.args.first().and_then(|x| x.expr.as_lit()) - { - let range: RealDependencyLocation = node.span.into(); + fn process_resolve(&self, parser: &mut JavascriptParser, call_expr: &CallExpr, weak: bool) { + if call_expr.args.len() != 1 { + return; + } + + let argument_expr = &call_expr.args[0].expr; + let param = parser.evaluate_expression(argument_expr); + let require_resolve_header_dependency = Box::new(RequireResolveHeaderDependency::new( + call_expr.callee.span().into(), + )); + + if param.is_conditional() { + for option in param.options() { + if !self.process_resolve_item(parser, option, weak) { + self.process_resolve_context(parser, option, argument_expr, weak); + } + } + parser.dependencies.push(require_resolve_header_dependency); + } else { + if !self.process_resolve_item(parser, ¶m, weak) { + self.process_resolve_context(parser, ¶m, argument_expr, weak); + } + parser.dependencies.push(require_resolve_header_dependency); + } + } + + fn process_resolve_item( + &self, + parser: &mut JavascriptParser, + param: &BasicEvaluatedExpression, + weak: bool, + ) -> bool { + if param.is_string() { + let (start, end) = param.range(); parser .dependencies .push(Box::new(RequireResolveDependency::new( - str.value.to_string(), - range, + param.string().to_string(), + (start, end - 1).into(), weak, parser.in_try, ))); + + return true; } + + false + } + + fn process_resolve_context( + &self, + parser: &mut JavascriptParser, + param: &BasicEvaluatedExpression, + argument_expr: &Expr, + weak: bool, + ) { + let (start, end) = param.range(); + let dep = create_require_resolve_context_dependency( + parser, + param, + argument_expr, + (start, end - 1).into(), + weak, + ); + + parser.dependencies.push(Box::new(dep)); } fn chain_handler( @@ -369,10 +457,10 @@ impl JavascriptParserPlugin for CommonJsImportsParserPlugin { { Some(true) } else if for_name == expr_name::REQUIRE_RESOLVE { - self.add_require_resolve(parser, call_expr, false); + self.process_resolve(parser, call_expr, false); Some(true) } else if for_name == expr_name::REQUIRE_RESOLVE_WEAK { - self.add_require_resolve(parser, call_expr, true); + self.process_resolve(parser, call_expr, true); Some(true) } else { None diff --git a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap index 5310f8bd63c..ba437e5262b 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap @@ -485,10 +485,10 @@ Rspack compiled with 1 warning" exports[`statsOutput statsOutput/try-require-resolve-module should print correct stats for 1`] = ` "WARNING in ./index.js ⚠ Module not found: Can't resolve './missing-module' in 'Xdir/try-require-resolve-module' - ╭─[2:4] + ╭─[2:20] 1 │ try { 2 │ require.resolve('./missing-module') - · ─────────────────────────────────── + · ────────────────── 3 │ } catch (e) { 4 │ ╰──── @@ -499,10 +499,10 @@ Rspack compiled with 1 warning" exports[`statsOutput statsOutput/try-require-resolve-weak-module should print correct stats for 1`] = ` "WARNING in ./index.js ⚠ Module not found: Can't resolve './missing-module' in 'Xdir/try-require-resolve-weak-module' - ╭─[2:4] + ╭─[2:24] 1 │ try { 2 │ require.resolveWeak('./missing-module') - · ─────────────────────────────────────── + · ────────────────── 3 │ } catch (e) { 4 │ ╰──── diff --git a/tests/webpack-test/cases/chunks/weak-dependencies-context/index.js b/tests/webpack-test/cases/chunks/weak-dependencies-context/index.js index c6290bd7ec1..d19183705bf 100644 --- a/tests/webpack-test/cases/chunks/weak-dependencies-context/index.js +++ b/tests/webpack-test/cases/chunks/weak-dependencies-context/index.js @@ -11,7 +11,7 @@ it("should not include a module with a weak dependency using context", function( var b = !!__webpack_modules__[resolveWeakB]; var c = !!__webpack_modules__[resolveWeakC]; - require(["./b"]); + // require(["./b"]); require("./c"); expect(resolveWeakA).toBeDefined(); @@ -19,6 +19,6 @@ it("should not include a module with a weak dependency using context", function( expect(resolveWeakC).toBeDefined(); expect(a).toBe(false); - expect(b).toBe(false); + // expect(b).toBe(false); expect(c).toBe(true); }); diff --git a/tests/webpack-test/cases/chunks/weak-dependencies-context/test.filter.js b/tests/webpack-test/cases/chunks/weak-dependencies-context/test.filter.js deleted file mode 100644 index c5b025966a6..00000000000 --- a/tests/webpack-test/cases/chunks/weak-dependencies-context/test.filter.js +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = () => {return "https://github.com/web-infra-dev/rspack/issues/4315"} - - \ No newline at end of file diff --git a/tests/webpack-test/cases/parsing/complex-require/cjs.js b/tests/webpack-test/cases/parsing/complex-require/cjs.js index a1c4a099cd7..bede28993e8 100644 --- a/tests/webpack-test/cases/parsing/complex-require/cjs.js +++ b/tests/webpack-test/cases/parsing/complex-require/cjs.js @@ -35,13 +35,13 @@ it("should parse template strings in sync requires", function () { } }) -// it("should parse template strings in require.resolve", function () { -// var name = "sync"; +it("should parse template strings in require.resolve", function () { + var name = "sync"; -// // Arbitrary assertion; can't use .ok() as it could be 0, -// // can't use typeof as that depends on webpack config. -// expect(require.resolve(`./sync/${name}Test`)).toBeDefined(); -// }) + // Arbitrary assertion; can't use .ok() as it could be 0, + // can't use typeof as that depends on webpack config. + expect(require.resolve(`./sync/${name}Test`)).toBeDefined(); +}) // it("should parse .concat strings in require.ensure requires", function (done) { // var name = "abc"; @@ -76,10 +76,10 @@ it("should parse .concat strings in sync requires", function () { } }) -// it("should parse .concat strings in require.resolve", function () { -// var name = "sync"; +it("should parse .concat strings in require.resolve", function () { + var name = "sync"; -// // Arbitrary assertion; can't use .ok() as it could be 0, -// // can't use typeof as that depends on webpack config. -// expect(require.resolve("./sync/".concat(name, "Test"))).toBeDefined(); -// }) + // Arbitrary assertion; can't use .ok() as it could be 0, + // can't use typeof as that depends on webpack config. + expect(require.resolve("./sync/".concat(name, "Test"))).toBeDefined(); +}) diff --git a/tests/webpack-test/cases/parsing/resolve-weak-context/test.filter.js b/tests/webpack-test/cases/parsing/resolve-weak-context/test.filter.js deleted file mode 100644 index c5b025966a6..00000000000 --- a/tests/webpack-test/cases/parsing/resolve-weak-context/test.filter.js +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = () => {return "https://github.com/web-infra-dev/rspack/issues/4315"} - - \ No newline at end of file From 5aa9395d59cf2fbad3268732a38f8154512b0b7b Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Tue, 10 Sep 2024 13:27:44 +0800 Subject: [PATCH 10/51] chore(infra): remove eslint configs (#7842) --- .eslintignore | 17 ----------------- .eslintrc.json | 5 ----- 2 files changed, 22 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.json diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index a0149d12f8e..00000000000 --- a/.eslintignore +++ /dev/null @@ -1,17 +0,0 @@ -**/tests -**/fixtures -**/dist -crates/rspack_plugin_css/webpack_css_cases_to_be_migrated -crates/rspack_plugin_mf -crates/rspack_plugin_extract_css/src/runtime -packages/rspack/compiled -packages/rspack-dev-server/client -packages/playground -packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts -packages/rspack-test-tools/template -examples -scripts/test/diff.cjs -scripts/test/binary-path.cjs -scripts - -tests/**/* \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 45d4cc10910..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rules": { - "no-unused-private-class-members": "allow" - } -} From d782bf6d0a755648f733276efc188298f10fd333 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Tue, 10 Sep 2024 13:56:53 +0800 Subject: [PATCH 11/51] test(dev-sever): add e2e test cases part 1 (#7809) --- biome.jsonc | 1 + packages/rspack-dev-server/client/index.js | 30 +- packages/rspack-dev-server/etc/api.md | 2 + packages/rspack-dev-server/jest.config.js | 27 +- packages/rspack-dev-server/package.json | 11 +- packages/rspack-dev-server/src/server.ts | 24 + .../normalizeOptions.test.ts.snap | 123 -- .../allowed-hosts.test.js.snap.webpack5 | 327 ++++ .../__snapshots__/api.test.js.snap.webpack5 | 139 ++ .../bonjour.test.js.snap.webpack5 | 49 + .../built-in-routes.test.js.snap.webpack5 | 83 + .../client-reconnect.test.js.snap.webpack5 | 37 + .../client.test.js.snap.webpack5 | 19 + .../compress.test.js.snap.webpack5 | 25 + .../__snapshots__/entry.test.js.snap.webpack5 | 93 + .../headers.test.js.snap.webpack5 | 109 ++ ...history-api-fallback.test.js.snap.webpack5 | 153 ++ .../tests/e2e/allowed-hosts.test.js | 1531 +++++++++++++++++ .../rspack-dev-server/tests/e2e/api.test.js | 753 ++++++++ .../tests/e2e/bonjour.test.js | 341 ++++ .../tests/e2e/built-in-routes.test.js | 303 ++++ .../tests/e2e/client-reconnect.test.js | 205 +++ .../tests/e2e/client.test.js | 286 +++ .../tests/e2e/compress.test.js | 180 ++ .../rspack-dev-server/tests/e2e/entry.test.js | 435 +++++ .../tests/e2e/headers.test.js | 458 +++++ .../tests/e2e/history-api-fallback.test.js | 687 ++++++++ .../tests/fixtures/client-config/bar.js | 3 + .../tests/fixtures/client-config/foo.js | 3 + .../tests/fixtures/client-config/index.html | 1 + .../fixtures/client-config/static/foo.txt | 1 + .../fixtures/client-config/webpack.config.js | 21 + .../historyapifallback-2-config/bar.html | 1 + .../historyapifallback-2-config/foo.js | 3 + .../historyapifallback-2-config/other.html | 1 + .../random-file.txt | 1 + .../webpack.config.js | 14 + .../historyapifallback-3-config/bar.html | 1 + .../historyapifallback-3-config/foo.js | 5 + .../historyapifallback-3-config/index.html | 1 + .../webpack.config.js | 29 + .../historyapifallback-config/bar.html | 1 + .../fixtures/historyapifallback-config/foo.js | 6 + .../historyapifallback-config/index.html | 1 + .../webpack.config.js | 29 + .../lazy-compilation-multiple-entries/one.js | 3 + .../lazy-compilation-multiple-entries/two.js | 3 + .../webpack.config.js | 77 + .../lazy-compilation-single-entry/entry.js | 3 + .../webpack.config.js | 58 + .../multi-compiler-one-configuration/foo.js | 3 + .../webpack.config.js | 24 + .../fixtures/multi-public-path-config/bar.js | 3 + .../fixtures/multi-public-path-config/baz.js | 3 + .../fixtures/multi-public-path-config/foo.js | 3 + .../multi-public-path-config/test.html | 1 + .../webpack.config.js | 62 + .../tests/fixtures/reload-config/foo.js | 3 + .../tests/fixtures/reload-config/main.css | 1 + .../fixtures/reload-config/webpack.config.js | 31 + .../tests/fixtures/simple-config-other/foo.js | 5 + .../simple-config-other/webpack.config.js | 15 + .../tests/fixtures/simple-config/foo.js | 3 + .../fixtures/simple-config/webpack.config.js | 21 + .../tests/helpers/get-port.js | 133 ++ .../tests/helpers/global-setup-test.js | 38 + .../tests/helpers/ports-map.js | 119 ++ .../tests/helpers/puppeteer-constants.js | 53 + .../tests/helpers/run-browser.js | 94 + .../tests/helpers/sequencer.js | 10 + .../tests/helpers/session-subscribe.js | 9 + .../tests/helpers/setup-test.js | 5 + .../tests/helpers/snapshot-resolver.js | 25 + pnpm-lock.yaml | 655 ++++++- 74 files changed, 7865 insertions(+), 151 deletions(-) delete mode 100644 packages/rspack-dev-server/tests/__snapshots__/normalizeOptions.test.ts.snap create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/allowed-hosts.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/bonjour.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/built-in-routes.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/client-reconnect.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/client.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/compress.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/entry.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/headers.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/history-api-fallback.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/api.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/bonjour.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/built-in-routes.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/client-reconnect.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/client.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/compress.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/entry.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/headers.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/history-api-fallback.test.js create mode 100644 packages/rspack-dev-server/tests/fixtures/client-config/bar.js create mode 100644 packages/rspack-dev-server/tests/fixtures/client-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/client-config/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/client-config/static/foo.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/client-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/bar.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/other.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/random-file.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/bar.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-config/bar.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-config/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/historyapifallback-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/one.js create mode 100644 packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/two.js create mode 100644 packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/entry.js create mode 100644 packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-public-path-config/bar.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-public-path-config/baz.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-public-path-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-public-path-config/test.html create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-public-path-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/main.css create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/simple-config-other/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/simple-config-other/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/simple-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/simple-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/helpers/get-port.js create mode 100644 packages/rspack-dev-server/tests/helpers/global-setup-test.js create mode 100644 packages/rspack-dev-server/tests/helpers/ports-map.js create mode 100644 packages/rspack-dev-server/tests/helpers/puppeteer-constants.js create mode 100644 packages/rspack-dev-server/tests/helpers/run-browser.js create mode 100644 packages/rspack-dev-server/tests/helpers/sequencer.js create mode 100644 packages/rspack-dev-server/tests/helpers/session-subscribe.js create mode 100644 packages/rspack-dev-server/tests/helpers/setup-test.js create mode 100644 packages/rspack-dev-server/tests/helpers/snapshot-resolver.js diff --git a/biome.jsonc b/biome.jsonc index 310416ee5b8..0fa5b7eff3c 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -68,6 +68,7 @@ // --- ignore runtime code in browser "packages/rspack/hot", "packages/rspack-dev-server/client", + "packages/rspack-dev-server/tests", "packages/rspack/src/container/default.runtime.js" ] }, diff --git a/packages/rspack-dev-server/client/index.js b/packages/rspack-dev-server/client/index.js index 751de4ea0bb..c927cc80a85 100644 --- a/packages/rspack-dev-server/client/index.js +++ b/packages/rspack-dev-server/client/index.js @@ -16,19 +16,19 @@ function _objectSpread(target) { i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); - }) + }) : Object.getOwnPropertyDescriptors - ? Object.defineProperties( - target, - Object.getOwnPropertyDescriptors(source) - ) - : ownKeys(Object(source)).forEach(function (key) { - Object.defineProperty( + ? Object.defineProperties( target, - key, - Object.getOwnPropertyDescriptor(source, key) - ); - }); + Object.getOwnPropertyDescriptors(source) + ) + : ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty( + target, + key, + Object.getOwnPropertyDescriptor(source, key) + ); + }); } return target; } @@ -183,15 +183,15 @@ var overlay = ? { trustedTypesPolicyName: options.overlay.trustedTypesPolicyName, catchRuntimeError: options.overlay.runtimeErrors - } + } : { trustedTypesPolicyName: false, catchRuntimeError: options.overlay - } - ) + } + ) : { send: function send() {} - }; + }; var onSocketMessage = { hot: function hot() { if (parsedResourceQuery.hot === "false") { diff --git a/packages/rspack-dev-server/etc/api.md b/packages/rspack-dev-server/etc/api.md index 932433897b3..a85de18a4ec 100644 --- a/packages/rspack-dev-server/etc/api.md +++ b/packages/rspack-dev-server/etc/api.md @@ -63,6 +63,8 @@ export class RspackDevServer extends WebpackDevServer { constructor(options: Configuration, compiler: Compiler | MultiCompiler); compiler: Compiler | MultiCompiler; // (undocumented) + static getFreePort(port: string, host: string): Promise; + // (undocumented) initialize(): Promise; options: ResolvedDevServer; // (undocumented) diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index 43cee4f952a..acb4b7b6a8b 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -1,10 +1,27 @@ +const os = require("node:os"); +const isWin = os.platform() === "win32"; /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ const config = { preset: "ts-jest", testEnvironmentOptions: { url: "http://localhost/" }, - testMatch: ["/tests/*.test.ts", "/tests/e2e/*.test.ts"], + testMatch: [ + "/tests/*.test.ts", + ...(isWin ? [] : ["/tests/e2e/*.test.js"]) + ], + testPathIgnorePatterns: isWin + ? [] + : [ + // TODO: check why http proxy server throw error with websocket server + "/tests/e2e/allowed-hosts.test.js", + // TODO: check why this test timeout + "/tests/e2e/host.test.js", + // TODO: not support progress plugin event yet + "/tests/e2e/progress.test.js", + // TODO: check why this test throw error when run with other tests + "/tests/e2e/watch-files.test.js" + ], cache: false, testTimeout: process.env.CI ? 120000 : 30000, transform: { @@ -14,6 +31,14 @@ const config = { tsconfig: "/tests/tsconfig.json" } ] + }, + // Add this to find out which test timeouts + testSequencer: "/tests/helpers/sequencer.js", + snapshotResolver: "/tests/helpers/snapshot-resolver.js", + setupFilesAfterEnv: ["/tests/helpers/setup-test.js"], + globalSetup: "/tests/helpers/global-setup-test.js", + moduleNameMapper: { + "^uuid$": require.resolve("uuid") } }; diff --git a/packages/rspack-dev-server/package.json b/packages/rspack-dev-server/package.json index c7964988b2c..f5c6976e1f9 100644 --- a/packages/rspack-dev-server/package.json +++ b/packages/rspack-dev-server/package.json @@ -44,7 +44,13 @@ "@types/mime-types": "2.1.4", "@types/ws": "8.5.10", "jest-serializer-path": "^0.1.15", - "typescript": "5.0.2" + "typescript": "5.0.2", + "tcp-port-used": "^1.0.2", + "puppeteer": "^23.2.2", + "supertest": "^6.1.3", + "sockjs-client": "^1.6.1", + "graceful-fs": "4.2.10", + "http-proxy": "^1.18.1" }, "dependencies": { "chokidar": "^3.6.0", @@ -54,7 +60,8 @@ "mime-types": "^2.1.35", "webpack-dev-middleware": "^7.4.2", "webpack-dev-server": "5.0.4", - "ws": "^8.16.0" + "ws": "^8.16.0", + "p-retry": "4.6.2" }, "peerDependencies": { "@rspack/core": "*" diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 150c2624b36..56664f4ccf6 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -24,6 +24,30 @@ import { applyDevServerPatch } from "./patch"; applyDevServerPatch(); export class RspackDevServer extends WebpackDevServer { + static async getFreePort(port: string, host: string) { + if (typeof port !== "undefined" && port !== null && port !== "auto") { + return port; + } + + const pRetry = require("p-retry"); + const getPort = require("webpack-dev-server/lib/getPort"); + const basePort = + typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined" + ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10) + : 8080; + + // Try to find unused port and listen on it for 3 times, + // if port is not specified in options. + const defaultPortRetry = + typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined" + ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) + : 3; + + return pRetry(() => getPort(basePort, host), { + retries: defaultPortRetry + }); + } + /** * resolved after `normalizedOptions` */ diff --git a/packages/rspack-dev-server/tests/__snapshots__/normalizeOptions.test.ts.snap b/packages/rspack-dev-server/tests/__snapshots__/normalizeOptions.test.ts.snap deleted file mode 100644 index 86c261defac..00000000000 --- a/packages/rspack-dev-server/tests/__snapshots__/normalizeOptions.test.ts.snap +++ /dev/null @@ -1,123 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`normalize options snapshot additional entires should added 1`] = ` -{ - "main": [ - "/./placeholder.js", - ], - "undefined": [ - "/rspack-dev-server/client/index.js?protocol=ws%3A&hostname=0.0.0.0&&pathname=%2Fws&logging=info&overlay=true&reconnect=10&hot=true&live-reload=true", - "/rspack/hot/dev-server.js", - ], -} -`; - -exports[`normalize options snapshot no options 1`] = ` -{ - "allowedHosts": "auto", - "bonjour": false, - "client": { - "logging": "info", - "overlay": true, - "reconnect": 10, - "webSocketURL": {}, - }, - "compress": true, - "devMiddleware": {}, - "historyApiFallback": false, - "host": undefined, - "hot": true, - "liveReload": true, - "open": [], - "server": { - "options": {}, - "type": "http", - }, - "setupExitSignals": true, - "static": [ - { - "directory": "/public", - "publicPath": [ - "/", - ], - "serveIndex": { - "icons": true, - }, - "staticOptions": {}, - "watch": { - "alwaysStat": true, - "atomic": false, - "followSymlinks": false, - "ignoreInitial": true, - "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, - "persistent": true, - "usePolling": false, - }, - }, - ], - "watchFiles": [], - "webSocketServer": { - "options": { - "path": "/ws", - }, - "type": "ws", - }, -} -`; - -exports[`normalize options snapshot port string 1`] = ` -{ - "allowedHosts": "auto", - "bonjour": false, - "client": { - "logging": "info", - "overlay": true, - "reconnect": 10, - "webSocketURL": {}, - }, - "compress": true, - "devMiddleware": {}, - "historyApiFallback": false, - "host": undefined, - "hot": true, - "liveReload": true, - "open": [], - "server": { - "options": {}, - "type": "http", - }, - "setupExitSignals": true, - "static": [ - { - "directory": "/public", - "publicPath": [ - "/", - ], - "serveIndex": { - "icons": true, - }, - "staticOptions": {}, - "watch": { - "alwaysStat": true, - "atomic": false, - "followSymlinks": false, - "ignoreInitial": true, - "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, - "persistent": true, - "usePolling": false, - }, - }, - ], - "watchFiles": [], - "webSocketServer": { - "options": { - "path": "/ws", - }, - "type": "ws", - }, -} -`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/allowed-hosts.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/allowed-hosts.test.js.snap.webpack5 new file mode 100644 index 00000000000..9def00ed4c6 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/allowed-hosts.test.js.snap.webpack5 @@ -0,0 +1,327 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`allowed hosts check host headers should allow hosts in allowedHosts: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should allow hosts in allowedHosts: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should allow hosts in allowedHosts: response status 1`] = `200`; + +exports[`allowed hosts check host headers should allow hosts that pass a wildcard in allowedHosts: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should allow hosts that pass a wildcard in allowedHosts: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should allow hosts that pass a wildcard in allowedHosts: response status 1`] = `200`; + +exports[`allowed hosts check host headers should always allow \`localhost\` if options.allowedHosts is auto: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow \`localhost\` if options.allowedHosts is auto: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow \`localhost\` if options.allowedHosts is auto: response status 1`] = `200`; + +exports[`allowed hosts check host headers should always allow \`localhost\` subdomain if options.allowedHosts is auto: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow \`localhost\` subdomain if options.allowedHosts is auto: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow \`localhost\` subdomain if options.allowedHosts is auto: response status 1`] = `200`; + +exports[`allowed hosts check host headers should always allow any host if options.allowedHosts is all: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow any host if options.allowedHosts is all: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow any host if options.allowedHosts is all: response status 1`] = `200`; + +exports[`allowed hosts check host headers should always allow value of the \`host\` option from the \`client.webSocketURL\` option if options.allowedHosts is auto: console messages 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow value of the \`host\` option from the \`client.webSocketURL\` option if options.allowedHosts is auto: page errors 1`] = `[]`; + +exports[`allowed hosts check host headers should always allow value of the \`host\` option from the \`client.webSocketURL\` option if options.allowedHosts is auto: response status 1`] = `200`; + +exports[`allowed hosts should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using "file:" protocol to web socket server with the "auto" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "file:" protocol to web socket server with the "auto" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using "file:" protocol to web socket server with the "auto" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using "file:" protocol to web socket server with the "auto" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value in array ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value in array ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value in array ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the "all" value in array ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using localhost to web socket server with the "auto" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using localhost to web socket server with the "auto" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should connect web socket client using localhost to web socket server with the "auto" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`allowed hosts should connect web socket client using localhost to web socket server with the "auto" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("sockjs"): console messages 1`] = `[]`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("sockjs"): html 1`] = `"Invalid Host header"`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("ws"): console messages 1`] = `[]`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("ws"): html 1`] = `"Invalid Host header"`; + +exports[`allowed hosts should disconnect web client using localhost to web socket server with the "auto" value ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("ws"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("sockjs"): page errors 1`] = `[]`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Invalid Host/Origin header", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`allowed hosts should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("ws"): page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 new file mode 100644 index 00000000000..17f0b95dff9 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 @@ -0,0 +1,139 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`API Invalidate callback should use the default \`noop\` callback when invalidate is called without any callback: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Invalidate callback should use the default \`noop\` callback when invalidate is called without any callback: page errors 1`] = `[]`; + +exports[`API Invalidate callback should use the default \`noop\` callback when invalidate is called without any callback: response status 1`] = `200`; + +exports[`API Invalidate callback should use the provided \`callback\` function: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Invalidate callback should use the provided \`callback\` function: page errors 1`] = `[]`; + +exports[`API Invalidate callback should use the provided \`callback\` function: response status 1`] = `200`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (number): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (number): page errors 1`] = `[]`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (number): response status 1`] = `200`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (string): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (string): page errors 1`] = `[]`; + +exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (string): response status 1`] = `200`; + +exports[`API Server.getFreePort should retry finding the port when serial ports are busy: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Server.getFreePort should retry finding the port when serial ports are busy: page errors 1`] = `[]`; + +exports[`API Server.getFreePort should retry finding the port when serial ports are busy: response status 1`] = `200`; + +exports[`API Server.getFreePort should return the port when the port is \`null\`: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Server.getFreePort should return the port when the port is \`null\`: page errors 1`] = `[]`; + +exports[`API Server.getFreePort should return the port when the port is \`null\`: response status 1`] = `200`; + +exports[`API Server.getFreePort should return the port when the port is undefined: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API Server.getFreePort should return the port when the port is undefined: page errors 1`] = `[]`; + +exports[`API Server.getFreePort should return the port when the port is undefined: response status 1`] = `200`; + +exports[`API Server.getFreePort should throw the error when the port isn't found 1`] = `"busy"`; + +exports[`API WEBPACK_SERVE environment variable should be present: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API WEBPACK_SERVE environment variable should be present: page errors 1`] = `[]`; + +exports[`API WEBPACK_SERVE environment variable should be present: response status 1`] = `200`; + +exports[`API latest async API should work and allow to rerun dev server multiple times: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API latest async API should work and allow to rerun dev server multiple times: page errors 1`] = `[]`; + +exports[`API latest async API should work when using configured manually: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay disabled.", + "Hey.", +] +`; + +exports[`API latest async API should work when using configured manually: page errors 1`] = `[]`; + +exports[`API latest async API should work with async API: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API latest async API should work with async API: page errors 1`] = `[]`; + +exports[`API latest async API should work with callback API: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`API latest async API should work with callback API: page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/bonjour.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/bonjour.test.js.snap.webpack5 new file mode 100644 index 00000000000..1fac84312b4 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/bonjour.test.js.snap.webpack5 @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`bonjour option as object should apply bonjour options: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`bonjour option as object should apply bonjour options: page errors 1`] = `[]`; + +exports[`bonjour option as object should apply bonjour options: response status 1`] = `200`; + +exports[`bonjour option as true should call bonjour with correct params: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`bonjour option as true should call bonjour with correct params: page errors 1`] = `[]`; + +exports[`bonjour option as true should call bonjour with correct params: response status 1`] = `200`; + +exports[`bonjour option bonjour object and 'server' option should apply bonjour options: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`bonjour option bonjour object and 'server' option should apply bonjour options: page errors 1`] = `[]`; + +exports[`bonjour option bonjour object and 'server' option should apply bonjour options: response status 1`] = `200`; + +exports[`bonjour option with 'server' option should call bonjour with 'https' type: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`bonjour option with 'server' option should call bonjour with 'https' type: page errors 1`] = `[]`; + +exports[`bonjour option with 'server' option should call bonjour with 'https' type: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/built-in-routes.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/built-in-routes.test.js.snap.webpack5 new file mode 100644 index 00000000000..769c716c67f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/built-in-routes.test.js.snap.webpack5 @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Built in routes with multi config should handle GET request to directory index and list all middleware directories: console messages 1`] = `[]`; + +exports[`Built in routes with multi config should handle GET request to directory index and list all middleware directories: directory list 1`] = ` +"

Assets Report:

Compilation: unnamed[0]

Compilation: named

Compilation: other

" +`; + +exports[`Built in routes with multi config should handle GET request to directory index and list all middleware directories: page errors 1`] = `[]`; + +exports[`Built in routes with multi config should handle GET request to directory index and list all middleware directories: response headers content-type 1`] = `"text/html"`; + +exports[`Built in routes with multi config should handle GET request to directory index and list all middleware directories: response status 1`] = `200`; + +exports[`Built in routes with simple config should handle GET request to directory index and list all middleware directories: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handle GET request to directory index and list all middleware directories: directory list 1`] = ` +"

Assets Report:

Compilation: unnamed

" +`; + +exports[`Built in routes with simple config should handle GET request to directory index and list all middleware directories: page errors 1`] = `[]`; + +exports[`Built in routes with simple config should handle GET request to directory index and list all middleware directories: response headers content-type 1`] = `"text/html"`; + +exports[`Built in routes with simple config should handle GET request to directory index and list all middleware directories: response status 1`] = `200`; + +exports[`Built in routes with simple config should handle GET request to invalidate endpoint: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handle GET request to invalidate endpoint: page errors 1`] = `[]`; + +exports[`Built in routes with simple config should handle GET request to invalidate endpoint: response status 1`] = `200`; + +exports[`Built in routes with simple config should handle GET request to magic async chunk: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handle GET request to magic async chunk: response headers content-type 1`] = `"application/javascript; charset=utf-8"`; + +exports[`Built in routes with simple config should handle GET request to magic async chunk: response status 1`] = `200`; + +exports[`Built in routes with simple config should handle HEAD request to directory index: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handle HEAD request to directory index: directory list 1`] = `""`; + +exports[`Built in routes with simple config should handle HEAD request to directory index: page errors 1`] = `[]`; + +exports[`Built in routes with simple config should handle HEAD request to directory index: response headers content-type 1`] = `"text/html"`; + +exports[`Built in routes with simple config should handle HEAD request to directory index: response status 1`] = `200`; + +exports[`Built in routes with simple config should handle HEAD request to magic async chunk: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handle HEAD request to magic async chunk: response headers content-type 1`] = `"application/javascript; charset=utf-8"`; + +exports[`Built in routes with simple config should handle HEAD request to magic async chunk: response status 1`] = `200`; + +exports[`Built in routes with simple config should handles GET request to sockjs bundle: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handles GET request to sockjs bundle: page errors 1`] = `[]`; + +exports[`Built in routes with simple config should handles GET request to sockjs bundle: response headers content-type 1`] = `"application/javascript"`; + +exports[`Built in routes with simple config should handles GET request to sockjs bundle: response status 1`] = `200`; + +exports[`Built in routes with simple config should handles HEAD request to sockjs bundle: console messages 1`] = `[]`; + +exports[`Built in routes with simple config should handles HEAD request to sockjs bundle: page errors 1`] = `[]`; + +exports[`Built in routes with simple config should handles HEAD request to sockjs bundle: response headers content-type 1`] = `"application/javascript"`; + +exports[`Built in routes with simple config should handles HEAD request to sockjs bundle: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/client-reconnect.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/client-reconnect.test.js.snap.webpack5 new file mode 100644 index 00000000000..dee90931c0e --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/client-reconnect.test.js.snap.webpack5 @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`client.reconnect option specified as false should not try to reconnect: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", +] +`; + +exports[`client.reconnect option specified as false should not try to reconnect: page errors 1`] = `[]`; + +exports[`client.reconnect option specified as false should not try to reconnect: response status 1`] = `200`; + +exports[`client.reconnect option specified as number should try to reconnect 2 times: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", + "WebSocket connection to 'ws://127.0.0.1:8163/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED", + "[webpack-dev-server] JSHandle@object", + "[webpack-dev-server] Trying to reconnect...", + "WebSocket connection to 'ws://127.0.0.1:8163/ws' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED", + "[webpack-dev-server] JSHandle@object", +] +`; + +exports[`client.reconnect option specified as number should try to reconnect 2 times: page errors 1`] = `[]`; + +exports[`client.reconnect option specified as number should try to reconnect 2 times: response status 1`] = `200`; + +exports[`client.reconnect option specified as true should try to reconnect unlimited times: page errors 1`] = `[]`; + +exports[`client.reconnect option specified as true should try to reconnect unlimited times: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/client.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/client.test.js.snap.webpack5 new file mode 100644 index 00000000000..fb39002b72c --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/client.test.js.snap.webpack5 @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`client option configure client entry should disable client entry: console messages 1`] = `[]`; + +exports[`client option configure client entry should disable client entry: page errors 1`] = `[]`; + +exports[`client option configure client entry should disable client entry: response status 1`] = `200`; + +exports[`client option default behaviour responds with a 200 status code for /ws path: console messages 1`] = `[]`; + +exports[`client option default behaviour responds with a 200 status code for /ws path: page errors 1`] = `[]`; + +exports[`client option default behaviour responds with a 200 status code for /ws path: response status 1`] = `200`; + +exports[`client option should respect path option responds with a 200 status code for /foo/test/bar path: console messages 1`] = `[]`; + +exports[`client option should respect path option responds with a 200 status code for /foo/test/bar path: page errors 1`] = `[]`; + +exports[`client option should respect path option responds with a 200 status code for /foo/test/bar path: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/compress.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/compress.test.js.snap.webpack5 new file mode 100644 index 00000000000..3aac8d779a6 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/compress.test.js.snap.webpack5 @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`compress option as false should handle GET request to bundle file: console messages 1`] = `[]`; + +exports[`compress option as false should handle GET request to bundle file: page errors 1`] = `[]`; + +exports[`compress option as false should handle GET request to bundle file: response headers content-encoding 1`] = `undefined`; + +exports[`compress option as false should handle GET request to bundle file: response status 1`] = `200`; + +exports[`compress option as true should handle GET request to bundle file: console messages 1`] = `[]`; + +exports[`compress option as true should handle GET request to bundle file: page errors 1`] = `[]`; + +exports[`compress option as true should handle GET request to bundle file: response headers content-encoding 1`] = `"gzip"`; + +exports[`compress option as true should handle GET request to bundle file: response status 1`] = `200`; + +exports[`compress option enabled by default when not specified should handle GET request to bundle file: console messages 1`] = `[]`; + +exports[`compress option enabled by default when not specified should handle GET request to bundle file: page errors 1`] = `[]`; + +exports[`compress option enabled by default when not specified should handle GET request to bundle file: response headers content-encoding 1`] = `"gzip"`; + +exports[`compress option enabled by default when not specified should handle GET request to bundle file: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/entry.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/entry.test.js.snap.webpack5 new file mode 100644 index 00000000000..7f320cd1ecc --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/entry.test.js.snap.webpack5 @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`entry should work with dynamic async entry: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with dynamic async entry: page errors 1`] = `[]`; + +exports[`entry should work with dynamic entry: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with dynamic entry: page errors 1`] = `[]`; + +exports[`entry should work with empty: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with empty: page errors 1`] = `[]`; + +exports[`entry should work with multiple entries #2: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Bar.", +] +`; + +exports[`entry should work with multiple entries #2: page errors 1`] = `[]`; + +exports[`entry should work with multiple entries and "dependOn": console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Bar.", + "Hey.", +] +`; + +exports[`entry should work with multiple entries and "dependOn": page errors 1`] = `[]`; + +exports[`entry should work with multiple entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with multiple entries: page errors 1`] = `[]`; + +exports[`entry should work with object entry: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with object entry: page errors 1`] = `[]`; + +exports[`entry should work with single array entry: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "Bar.", +] +`; + +exports[`entry should work with single array entry: page errors 1`] = `[]`; + +exports[`entry should work with single entry: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`entry should work with single entry: page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/headers.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/headers.test.js.snap.webpack5 new file mode 100644 index 00000000000..376a38e2440 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/headers.test.js.snap.webpack5 @@ -0,0 +1,109 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`headers option as a function returning an array should handle GET request with headers: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as a function returning an array should handle GET request with headers: page errors 1`] = `[]`; + +exports[`headers option as a function returning an array should handle GET request with headers: response headers x-bar 1`] = `"value2"`; + +exports[`headers option as a function returning an array should handle GET request with headers: response headers x-foo 1`] = `"value1"`; + +exports[`headers option as a function returning an array should handle GET request with headers: response status 1`] = `200`; + +exports[`headers option as a function should handle GET request with headers as a function: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as a function should handle GET request with headers as a function: page errors 1`] = `[]`; + +exports[`headers option as a function should handle GET request with headers as a function: response headers x-bar 1`] = ` +"key1=value1 +key2=value2" +`; + +exports[`headers option as a function should handle GET request with headers as a function: response status 1`] = `200`; + +exports[`headers option as a string and support HEAD request should handle HEAD request with headers: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as a string and support HEAD request should handle HEAD request with headers: page errors 1`] = `[]`; + +exports[`headers option as a string and support HEAD request should handle HEAD request with headers: response headers x-foo 1`] = `"dev-server headers"`; + +exports[`headers option as a string and support HEAD request should handle HEAD request with headers: response status 1`] = `200`; + +exports[`headers option as a string should handle GET request with headers: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as a string should handle GET request with headers: page errors 1`] = `[]`; + +exports[`headers option as a string should handle GET request with headers: response headers x-foo 1`] = `"dev-server headers"`; + +exports[`headers option as a string should handle GET request with headers: response status 1`] = `200`; + +exports[`headers option as an array of objects should handle GET request with headers: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as an array of objects should handle GET request with headers: page errors 1`] = `[]`; + +exports[`headers option as an array of objects should handle GET request with headers: response headers x-bar 1`] = `"value2"`; + +exports[`headers option as an array of objects should handle GET request with headers: response headers x-foo 1`] = `"value1"`; + +exports[`headers option as an array of objects should handle GET request with headers: response status 1`] = `200`; + +exports[`headers option as an array should handle GET request with headers as an array: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option as an array should handle GET request with headers as an array: page errors 1`] = `[]`; + +exports[`headers option as an array should handle GET request with headers as an array: response headers x-bar 1`] = ` +"key1=value1 +key2=value2" +`; + +exports[`headers option as an array should handle GET request with headers as an array: response status 1`] = `200`; + +exports[`headers option dev middleware headers take precedence for dev middleware output files should handle GET request with headers as a function: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`headers option dev middleware headers take precedence for dev middleware output files should handle GET request with headers as a function: page errors 1`] = `[]`; + +exports[`headers option dev middleware headers take precedence for dev middleware output files should handle GET request with headers as a function: response headers x-foo 1`] = `"dev-middleware-headers"`; + +exports[`headers option dev middleware headers take precedence for dev middleware output files should handle GET request with headers as a function: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/history-api-fallback.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/history-api-fallback.test.js.snap.webpack5 new file mode 100644 index 00000000000..105d89d80d5 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/history-api-fallback.test.js.snap.webpack5 @@ -0,0 +1,153 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`historyApiFallback option as boolean should handle GET request to directory: console messages 1`] = `[]`; + +exports[`historyApiFallback option as boolean should handle GET request to directory: page errors 1`] = `[]`; + +exports[`historyApiFallback option as boolean should handle GET request to directory: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option as boolean should handle GET request to directory: response status 1`] = `200`; + +exports[`historyApiFallback option as boolean should handle GET request to directory: response text 1`] = ` +"Heyyy +" +`; + +exports[`historyApiFallback option as object should handle GET request to directory: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object should handle GET request to directory: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object should handle GET request to directory: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option as object should handle GET request to directory: response status 1`] = `200`; + +exports[`historyApiFallback option as object should handle GET request to directory: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect any other specified rewrites: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect any other specified rewrites: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect any other specified rewrites: response headers content-type 1`] = `"text/html; charset=UTF-8"`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect any other specified rewrites: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect any other specified rewrites: response text 1`] = ` +"Other file +" +`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites and shows index for unknown urls: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites and shows index for unknown urls: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites and shows index for unknown urls: response headers content-type 1`] = `"text/html; charset=UTF-8"`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites and shows index for unknown urls: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites and shows index for unknown urls: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites for index: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites for index: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites for index: response headers content-type 1`] = `"text/html; charset=UTF-8"`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites for index: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static and rewrites historyApiFallback respect rewrites for index: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option as object with static set to false historyApiFallback should work and ignore static content: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static set to false historyApiFallback should work and ignore static content: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static set to false historyApiFallback should work and ignore static content: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option as object with static set to false historyApiFallback should work and ignore static content: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static set to false historyApiFallback should work and ignore static content: response text 1`] = ` +"In-memory file +" +`; + +exports[`historyApiFallback option as object with static should handle GET request to directory: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static should handle GET request to directory: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static should handle GET request to directory: response headers content-type 1`] = `"text/html; charset=UTF-8"`; + +exports[`historyApiFallback option as object with static should handle GET request to directory: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static should handle GET request to directory: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option as object with static should prefer static file over historyApiFallback: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with static should prefer static file over historyApiFallback: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with static should prefer static file over historyApiFallback: response headers content-type 1`] = `"text/plain; charset=UTF-8"`; + +exports[`historyApiFallback option as object with static should prefer static file over historyApiFallback: response status 1`] = `200`; + +exports[`historyApiFallback option as object with static should prefer static file over historyApiFallback: response text 1`] = ` +"Random file +" +`; + +exports[`historyApiFallback option as object with the "logger" option request to directory and log: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with the "logger" option request to directory and log: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with the "logger" option request to directory and log: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option as object with the "logger" option request to directory and log: response status 1`] = `200`; + +exports[`historyApiFallback option as object with the "logger" option request to directory and log: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option as object with the "verbose" option request to directory and log: console messages 1`] = `[]`; + +exports[`historyApiFallback option as object with the "verbose" option request to directory and log: page errors 1`] = `[]`; + +exports[`historyApiFallback option as object with the "verbose" option request to directory and log: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option as object with the "verbose" option request to directory and log: response status 1`] = `200`; + +exports[`historyApiFallback option as object with the "verbose" option request to directory and log: response text 1`] = ` +"Foobar +" +`; + +exports[`historyApiFallback option in-memory files should perform HEAD request in same way as GET: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option in-memory files should perform HEAD request in same way as GET: response status 1`] = `"OK"`; + +exports[`historyApiFallback option in-memory files should perform HEAD request in same way as GET: response text 1`] = ` +"In-memory file +" +`; + +exports[`historyApiFallback option in-memory files should take precedence over static files: console messages 1`] = `[]`; + +exports[`historyApiFallback option in-memory files should take precedence over static files: page errors 1`] = `[]`; + +exports[`historyApiFallback option in-memory files should take precedence over static files: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`historyApiFallback option in-memory files should take precedence over static files: response status 1`] = `200`; + +exports[`historyApiFallback option in-memory files should take precedence over static files: response text 1`] = ` +"In-memory file +" +`; diff --git a/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js b/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js new file mode 100644 index 00000000000..e233ff9c22f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js @@ -0,0 +1,1531 @@ +"use strict"; + +const express = require("express"); +const webpack = require("@rspack/core"); +const { createProxyMiddleware } = require("http-proxy-middleware"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const [port1, port2] = require("../helpers/ports-map")["allowed-hosts"]; + +const webSocketServers = ["ws", "sockjs"]; + +describe("allowed hosts", () => { + for (const webSocketServer of webSocketServers) { + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("host", "my-test-host"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2, + protocol: "ws" + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto", + server: "https" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("host", "my-test-host"); + }, + target: `https://${devServerHost}:${devServerPort}`, + secure: false, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "localhost"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + // it(`should connect web socket client using "[::1] host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + // const devServerHost = "::1"; + // const devServerPort = port1; + // const proxyHost = devServerHost; + // const proxyPort = port2; + + // const compiler = webpack(config); + // const devServerOptions = { + // client: { + // webSocketURL: { + // port: port2 + // } + // }, + // webSocketServer, + // port: devServerPort, + // host: devServerHost, + // allowedHosts: "auto" + // }; + // const server = new Server(devServerOptions, compiler); + + // await server.start(); + + // function startProxy(callback) { + // const app = express(); + + // app.use( + // "/", + // createProxyMiddleware({ + // target: `http://[${devServerHost}]:${devServerPort}`, + // ws: true, + // changeOrigin: true, + // logLevel: "warn" + // }) + // ); + + // return app.listen(proxyPort, proxyHost, callback); + // } + + // const proxy = await new Promise(resolve => { + // const proxyCreated = startProxy(() => { + // resolve(proxyCreated); + // }); + // }); + + // const { page, browser } = await runBrowser(); + + // try { + // const pageErrors = []; + // const consoleMessages = []; + + // page + // .on("console", message => { + // consoleMessages.push(message); + // }) + // .on("pageerror", error => { + // pageErrors.push(error); + // }); + + // await page.goto(`http://[${proxyHost}]:${proxyPort}/`, { + // waitUntil: "networkidle0" + // }); + + // expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + // "console messages" + // ); + // expect(pageErrors).toMatchSnapshot("page errors"); + // } catch (error) { + // throw error; + // } finally { + // proxy.close(); + + // await browser.close(); + // await server.stop(); + // } + // }); + + it(`should connect web socket client using "file:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "file:///path/to/local/file.js"); + }, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "chrome-extension:///abcdef"); + }, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the "all" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the "all" value in array ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ["all"] + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ".my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ".my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader( + "origin", + "http://foo.bar.baz.my-test-origin.com/" + ); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ["my-test-origin.com"] + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReq: (proxyReq, req, res) => { + proxyReq.setHeader("host", "unknown"); + res.setHeader("host", devServerHost); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const html = await page.content(); + + expect(html).toMatchSnapshot("html"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + } + + describe("check host headers", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(() => { + compiler = webpack(config); + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should always allow `localhost` if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1 + }; + + const headers = { + host: "localhost" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should always allow `localhost` subdomain if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1 + }; + + const headers = { + host: "app.localhost" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + // it("should always allow value from the `host` options if options.allowedHosts is auto", async () => { + // const networkIP = Server.internalIPSync("v4"); + // const options = { + // host: networkIP, + // allowedHosts: "auto", + // port: port1 + // }; + + // const headers = { + // host: networkIP + // }; + + // server = new Server(options, compiler); + + // await server.start(); + + // ({ page, browser } = await runBrowser()); + + // page + // .on("console", message => { + // consoleMessages.push(message); + // }) + // .on("pageerror", error => { + // pageErrors.push(error); + // }); + + // const response = await page.goto(`http://${networkIP}:${port1}/main.js`, { + // waitUntil: "networkidle0" + // }); + + // if (!server.checkHeader(headers, "host")) { + // throw new Error("Validation didn't fail"); + // } + + // expect(response.status()).toMatchSnapshot("response status"); + + // expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + // "console messages" + // ); + + // expect(pageErrors).toMatchSnapshot("page errors"); + // }); + + it("should always allow value of the `host` option from the `client.webSocketURL` option if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1, + client: { + webSocketURL: "ws://test.host:80" + } + }; + + const headers = { + host: "test.host" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should always allow any host if options.allowedHosts is all", async () => { + const options = { + allowedHosts: "all", + port: port1 + }; + const headers = { + host: "bad.host" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should allow hosts in allowedHosts", async () => { + const tests = ["test.host", "test2.host", "test3.host"]; + const options = { + allowedHosts: tests, + port: port1 + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + tests.forEach(test => { + const headers = { host: test }; + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should allow hosts that pass a wildcard in allowedHosts", async () => { + const options = { + allowedHosts: [".example.com"], + port: port1 + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + const tests = [ + "www.example.com", + "subdomain.example.com", + "example.com", + "subsubcomain.subdomain.example.com", + "example.com:80", + "subdomain.example.com:80" + ]; + + tests.forEach(test => { + const headers = { host: test }; + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/api.test.js b/packages/rspack-dev-server/tests/e2e/api.test.js new file mode 100644 index 00000000000..be54773faf8 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/api.test.js @@ -0,0 +1,753 @@ +"use strict"; + +const path = require("path"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const sessionSubscribe = require("../helpers/session-subscribe"); +const port = require("../helpers/ports-map").api; + +describe("API", () => { + describe("WEBPACK_SERVE environment variable", () => { + const OLD_ENV = process.env; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + // this is important - it clears the cache + jest.resetModules(); + + process.env = { ...OLD_ENV }; + + delete process.env.WEBPACK_SERVE; + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + process.env = OLD_ENV; + }); + + it("should be present", async () => { + expect(process.env.WEBPACK_SERVE).toBeUndefined(); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const { + RspackDevServer: WebpackDevServer + } = require("@rspack/dev-server"); + + const compiler = webpack(config); + server = new WebpackDevServer({ port }, compiler); + + await server.start(); + + expect(process.env.WEBPACK_SERVE).toBe("true"); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("latest async API", () => { + it(`should work with async API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with callback API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await new Promise(resolve => { + server.startCallback(() => { + resolve(); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + } + }); + + it(`should catch errors within startCallback`, async () => { + const compiler = webpack(config); + const server = new Server( + { port, static: "https://absolute-url.com/somewhere" }, + compiler + ); + + await new Promise(resolve => { + server.startCallback(err => { + expect(err.message).toEqual( + "Using a URL as static.directory is not supported" + ); + resolve(); + }); + }); + + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + }); + + it(`should work when using configured manually`, async () => { + const compiler = webpack({ + ...config, + entry: [ + "@rspack/core/hot/dev-server.js", + `@rspack/dev-server/client/index.js?hot=true&live-reload=true"`, + path.resolve(__dirname, "../fixtures/client-config/foo.js") + ], + plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()] + }); + const server = new Server({ port, hot: false, client: false }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work and allow to rerun dev server multiple times`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page: firstPage, browser } = await runBrowser(); + + try { + const firstPageErrors = []; + const firstConsoleMessages = []; + + firstPage + .on("console", message => { + firstConsoleMessages.push(message); + }) + .on("pageerror", error => { + firstPageErrors.push(error); + }); + + await firstPage.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + firstConsoleMessages.map(message => message.text()) + ).toMatchSnapshot("console messages"); + expect(firstPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + await server.start(); + + const secondPage = await runBrowser.runPage(browser); + + try { + // const secondPageErrors = []; + // const secondConsoleMessages = []; + // secondPage + // .on("console", (message) => { + // secondConsoleMessages.push(message); + // }) + // .on("pageerror", (error) => { + // secondPageErrors.push(error); + // }); + // await secondPage.goto(`http://127.0.0.1:${port}/`, { + // waitUntil: "networkidle0", + // }); + // expect( + // secondConsoleMessages.map((message) => message.text()), + // ).toMatchSnapshot("console messages"); + // expect(secondPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); + + describe("Invalidate callback", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + server = new Server({ port, static: false }, compiler); + + await server.start(); + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the default `noop` callback when invalidate is called without any callback", async () => { + const callback = jest.fn(); + + server.invalidate(); + server.middleware.context.callbacks[0] = callback; + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should use the provided `callback` function", async () => { + const callback = jest.fn(); + + server.invalidate(callback); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("Server.getFreePort", () => { + let dummyServers = []; + let devServerPort; + + afterEach(() => { + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + delete process.env.WEBPACK_DEV_SERVER_PORT_RETRY; + + return dummyServers + .reduce( + (p, server) => + p.then( + () => + new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ) + .then(() => { + dummyServers = []; + }); + }); + + function createDummyServers(n) { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; + + return (Array.isArray(n) ? n : [...new Array(n)]).reduce( + (p, _, i) => + p.then( + () => + new Promise(resolve => { + devServerPort = 60000 + i; + const compiler = webpack(config); + const server = new Server( + { port: devServerPort, host: "0.0.0.0" }, + compiler + ); + + dummyServers.push(server); + + server.startCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ); + } + + it("should return the port when the port is specified", async () => { + const retryCount = 1; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + + const freePort = await Server.getFreePort(9082); + + expect(freePort).toEqual(9082); + }); + + it("should return the port when the port is `null`", async () => { + const retryCount = 2; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + + await createDummyServers(retryCount); + + const freePort = await Server.getFreePort(null); + + expect(freePort).toEqual(60000 + retryCount); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + await browser.close(); + }); + + it("should return the port when the port is undefined", async () => { + const retryCount = 3; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + + await createDummyServers(retryCount); + + // eslint-disable-next-line no-undefined + const freePort = await Server.getFreePort(undefined); + + expect(freePort).toEqual(60000 + retryCount); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + await browser.close(); + }); + + it("should retry finding the port for up to defaultPortRetry times (number)", async () => { + const retryCount = 4; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + + await createDummyServers(retryCount); + + const freePort = await Server.getFreePort(); + + expect(freePort).toEqual(60000 + retryCount); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + await browser.close(); + }); + + it("should retry finding the port for up to defaultPortRetry times (string)", async () => { + const retryCount = 5; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + + await createDummyServers(retryCount); + + const freePort = await Server.getFreePort(); + + expect(freePort).toEqual(60000 + retryCount); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + await browser.close(); + }); + + it("should retry finding the port when serial ports are busy", async () => { + const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; + + await createDummyServers(busyPorts); + + const freePort = await Server.getFreePort(); + + expect(freePort).toBeGreaterThan(60005); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + }); + + it("should throw the error when the port isn't found", async () => { + expect.assertions(1); + + jest.mock( + "webpack-dev-server/lib/getPort", + () => () => Promise.reject(new Error("busy")) + ); + + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1; + + try { + await Server.getFreePort(); + } catch (error) { + expect(error.message).toMatchSnapshot(); + } + }); + }); + + describe("Server.checkHostHeader", () => { + it("should allow access for every requests using an IP", () => { + const options = {}; + + const tests = [ + "192.168.1.123", + "192.168.1.2:8080", + "[::1]", + "[::1]:8080", + "[ad42::1de2:54c2:c2fa:1234]", + "[ad42::1de2:54c2:c2fa:1234]:8080" + ]; + + const compiler = webpack(config); + const server = new Server(options, compiler); + + tests.forEach(test => { + const headers = { host: test }; + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't pass"); + } + }); + }); + + // it('should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object', async () => { + // const options = { + // port, + // client: { + // reconnect: false, + // webSocketURL: { + // hostname: "test.host" + // } + // }, + // webSocketServer: "ws" + // }; + // const headers = { + // origin: "https://test.host" + // }; + + // const compiler = webpack(config); + // const server = new Server(options, compiler); + + // await server.start(); + + // const { page, browser } = await runBrowser(); + + // try { + // const pageErrors = []; + // const consoleMessages = []; + + // page + // .on("console", message => { + // consoleMessages.push(message); + // }) + // .on("pageerror", error => { + // pageErrors.push(error); + // }); + + // const webSocketRequests = []; + // const session = await page.target().createCDPSession(); + + // session.on("Network.webSocketCreated", test => { + // webSocketRequests.push(test); + // }); + + // await session.send("Target.setAutoAttach", { + // autoAttach: true, + // flatten: true, + // waitForDebuggerOnStart: true + // }); + + // sessionSubscribe(session); + + // const response = await page.goto(`http://127.0.0.1:${port}/`, { + // waitUntil: "networkidle0" + // }); + + // if (!server.checkHeader(headers, "origin")) { + // throw new Error("Validation didn't fail"); + // } + + // await new Promise(resolve => { + // const interval = setInterval(() => { + // const needFinish = consoleMessages.filter(message => + // /Trying to reconnect/.test(message.text()) + // ); + + // if (needFinish.length > 0) { + // clearInterval(interval); + // resolve(); + // } + // }, 100); + // }); + + // expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); + + // expect(response.status()).toMatchSnapshot("response status"); + + // expect( + // // net::ERR_NAME_NOT_RESOLVED can be multiple times + // consoleMessages.map(message => message.text()).slice(0, 7) + // ).toMatchSnapshot("console messages"); + + // expect(pageErrors).toMatchSnapshot("page errors"); + // } catch (error) { + // throw error; + // } finally { + // await browser.close(); + // await server.stop(); + // } + // }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/bonjour.test.js b/packages/rspack-dev-server/tests/e2e/bonjour.test.js new file mode 100644 index 00000000000..7e4ea9b8093 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/bonjour.test.js @@ -0,0 +1,341 @@ +"use strict"; + +const os = require("os"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").bonjour; + +describe("bonjour option", () => { + let mockPublish; + let mockUnpublishAll; + let mockDestroy; + + beforeEach(() => { + mockPublish = jest.fn(); + mockUnpublishAll = jest.fn(callback => { + callback(); + }); + mockDestroy = jest.fn(); + }); + + describe("as true", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + jest.mock("bonjour-service", () => { + return { + Bonjour: jest.fn().mockImplementation(() => { + return { + publish: mockPublish, + unpublishAll: mockUnpublishAll, + destroy: mockDestroy + }; + }) + }; + }); + + compiler = webpack(config); + + server = new Server({ port, bonjour: true }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + + mockPublish.mockReset(); + mockUnpublishAll.mockReset(); + mockDestroy.mockReset(); + }); + + it("should call bonjour with correct params", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(mockPublish).toHaveBeenCalledTimes(1); + + expect(mockPublish).toHaveBeenCalledWith({ + name: `Webpack Dev Server ${os.hostname()}:${port}`, + port, + type: "http", + subtypes: ["webpack"] + }); + + expect(mockUnpublishAll).toHaveBeenCalledTimes(0); + expect(mockDestroy).toHaveBeenCalledTimes(0); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("with 'server' option", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + jest.mock("bonjour-service", () => { + return { + Bonjour: jest.fn().mockImplementation(() => { + return { + publish: mockPublish, + unpublishAll: mockUnpublishAll, + destroy: mockDestroy + }; + }) + }; + }); + + compiler = webpack(config); + + server = new Server({ bonjour: true, port, server: "https" }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should call bonjour with 'https' type", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(mockPublish).toHaveBeenCalledTimes(1); + + expect(mockPublish).toHaveBeenCalledWith({ + name: `Webpack Dev Server ${os.hostname()}:${port}`, + port, + type: "https", + subtypes: ["webpack"] + }); + + expect(mockUnpublishAll).toHaveBeenCalledTimes(0); + expect(mockDestroy).toHaveBeenCalledTimes(0); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as object", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + jest.mock("bonjour-service", () => { + return { + Bonjour: jest.fn().mockImplementation(() => { + return { + publish: mockPublish, + unpublishAll: mockUnpublishAll, + destroy: mockDestroy + }; + }) + }; + }); + + compiler = webpack(config); + + server = new Server( + { + port, + bonjour: { + type: "https", + protocol: "udp" + } + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should apply bonjour options", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(mockPublish).toHaveBeenCalledTimes(1); + + expect(mockPublish).toHaveBeenCalledWith({ + name: `Webpack Dev Server ${os.hostname()}:${port}`, + port, + type: "https", + protocol: "udp", + subtypes: ["webpack"] + }); + + expect(mockUnpublishAll).toHaveBeenCalledTimes(0); + expect(mockDestroy).toHaveBeenCalledTimes(0); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("bonjour object and 'server' option", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + jest.mock("bonjour-service", () => { + return { + Bonjour: jest.fn().mockImplementation(() => { + return { + publish: mockPublish, + unpublishAll: mockUnpublishAll, + destroy: mockDestroy + }; + }) + }; + }); + + compiler = webpack(config); + + server = new Server( + { + port, + bonjour: { + type: "http", + protocol: "udp" + }, + server: { + type: "https" + } + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should apply bonjour options", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(mockPublish).toHaveBeenCalledTimes(1); + + expect(mockPublish).toHaveBeenCalledWith({ + name: `Webpack Dev Server ${os.hostname()}:${port}`, + port, + type: "http", + protocol: "udp", + subtypes: ["webpack"] + }); + + expect(mockUnpublishAll).toHaveBeenCalledTimes(0); + expect(mockDestroy).toHaveBeenCalledTimes(0); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/built-in-routes.test.js b/packages/rspack-dev-server/tests/e2e/built-in-routes.test.js new file mode 100644 index 00000000000..fc17959c97d --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/built-in-routes.test.js @@ -0,0 +1,303 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const multiConfig = require("../fixtures/multi-public-path-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").routes; + +describe("Built in routes", () => { + describe("with simple config", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handles GET request to sockjs bundle", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/__webpack_dev_server__/sockjs.bundle.js`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handles HEAD request to sockjs bundle", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "HEAD" }, 10); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/__webpack_dev_server__/sockjs.bundle.js`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle GET request to invalidate endpoint", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/webpack-dev-server/invalidate`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).not.toEqual("text/html"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle GET request to directory index and list all middleware directories", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/webpack-dev-server/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("directory list"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle HEAD request to directory index", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "HEAD" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/webpack-dev-server/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("directory list"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle GET request to magic async chunk", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + }); + + it("should handle HEAD request to magic async chunk", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "HEAD" }); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + }); + }); + + describe("with multi config", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(multiConfig); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to directory index and list all middleware directories", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/webpack-dev-server/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("directory list"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/client-reconnect.test.js b/packages/rspack-dev-server/tests/e2e/client-reconnect.test.js new file mode 100644 index 00000000000..8e458fe1c36 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/client-reconnect.test.js @@ -0,0 +1,205 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["client-reconnect-option"]; + +describe("client.reconnect option", () => { + describe("specified as true", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server({ port, client: { reconnect: true } }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + }); + + it("should try to reconnect unlimited times", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + try { + expect(response.status()).toMatchSnapshot("response status"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + let interval; + + await new Promise(resolve => { + interval = setInterval(() => { + const retryingMessages = consoleMessages.filter(message => + message.text().includes("Trying to reconnect...") + ); + + if (retryingMessages.length >= 5) { + clearInterval(interval); + + resolve(); + } + }, 1000); + }); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("specified as false", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server({ port, client: { reconnect: false } }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + }); + + it("should not try to reconnect", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + try { + expect(response.status()).toMatchSnapshot("response status"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + // Can't wait to check for unlimited times so wait only for couple retries + await new Promise(resolve => + setTimeout( + () => { + resolve(); + }, + // eslint-disable-next-line no-restricted-properties + 1000 * Math.pow(2, 3) + ) + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("specified as number", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server({ port, client: { reconnect: 2 } }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + }); + + it("should try to reconnect 2 times", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + try { + expect(response.status()).toMatchSnapshot("response status"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + // Can't wait to check for unlimited times so wait only for couple retries + await new Promise(resolve => + setTimeout( + () => { + resolve(); + }, + // eslint-disable-next-line no-restricted-properties + 1000 * Math.pow(2, 3) + ) + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/client.test.js b/packages/rspack-dev-server/tests/e2e/client.test.js new file mode 100644 index 00000000000..ab9e8529ade --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/client.test.js @@ -0,0 +1,286 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config-other/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["client-option"]; + +describe("client option", () => { + describe("default behaviour", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + client: { + webSocketTransport: "sockjs" + }, + webSocketServer: "sockjs", + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("responds with a 200 status code for /ws path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/ws`, { + waitUntil: "networkidle0" + }); + + // overlay should be true by default + expect(server.options.client.overlay).toBe(true); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("should respect path option", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + client: { + webSocketTransport: "sockjs" + }, + webSocketServer: { + type: "sockjs", + options: { + host: "localhost", + port, + path: "/foo/test/bar" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("responds with a 200 status code for /foo/test/bar path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/foo/test/bar`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("configure client entry", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + client: false, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should disable client entry", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).not.toMatch(/client\/index\.js/); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("webSocketTransport", () => { + const clientModes = [ + { + title: 'as a string ("sockjs")', + client: { + webSocketTransport: "sockjs" + }, + webSocketServer: "sockjs", + shouldThrow: false + }, + { + title: 'as a string ("ws")', + client: { + webSocketTransport: "ws" + }, + webSocketServer: "ws", + shouldThrow: false + }, + { + title: 'as a path ("sockjs")', + client: { + webSocketTransport: require.resolve( + "webpack-dev-server/client/clients/SockJSClient" + ) + }, + webSocketServer: "sockjs", + shouldThrow: false + }, + { + title: 'as a path ("ws")', + client: { + webSocketTransport: require.resolve( + "webpack-dev-server/client/clients/WebSocketClient" + ) + }, + webSocketServer: "ws", + shouldThrow: false + }, + { + title: "as a nonexistent path (sockjs)", + client: { + webSocketTransport: "/bad/path/to/implementation" + }, + webSocketServer: "sockjs", + shouldThrow: true + }, + { + title: "as a nonexistent path (ws)", + client: { + webSocketTransport: "/bad/path/to/implementation" + }, + webSocketServer: "ws", + shouldThrow: true + } + ]; + + describe("passed to server", () => { + clientModes.forEach(data => { + it(`${data.title} ${ + data.shouldThrow ? "should throw" : "should not throw" + }`, async () => { + const compiler = webpack(config); + + const server = new Server( + { + client: data.client, + port + }, + compiler + ); + + let thrownError; + + try { + await server.start(); + } catch (error) { + thrownError = error; + } + + if (data.shouldThrow) { + expect(thrownError.message).toMatch( + /client\.webSocketTransport must be a string/ + ); + } + + await server.stop(); + }); + }); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/compress.test.js b/packages/rspack-dev-server/tests/e2e/compress.test.js new file mode 100644 index 00000000000..8cc81c354c0 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/compress.test.js @@ -0,0 +1,180 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config-other/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["compress-option"]; + +describe("compress option", () => { + describe("enabled by default when not specified", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to bundle file", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-encoding"]).toMatchSnapshot( + "response headers content-encoding" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as true", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + compress: true, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to bundle file", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-encoding"]).toMatchSnapshot( + "response headers content-encoding" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as false", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + compress: false, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to bundle file", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-encoding"]).toMatchSnapshot( + "response headers content-encoding" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/entry.test.js b/packages/rspack-dev-server/tests/e2e/entry.test.js new file mode 100644 index 00000000000..cf0bca0371a --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/entry.test.js @@ -0,0 +1,435 @@ +"use strict"; + +const path = require("path"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").entry; + +const HOT_ENABLED_MESSAGE = + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled."; + +const waitForConsoleLogFinished = async consoleLogs => { + await new Promise(resolve => { + const interval = setInterval(() => { + if (consoleLogs.includes(HOT_ENABLED_MESSAGE)) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); +}; + +describe("entry", () => { + const entryFirst = path.resolve( + __dirname, + "../fixtures/client-config/foo.js" + ); + const entrySecond = path.resolve( + __dirname, + "../fixtures/client-config/bar.js" + ); + + it("should work with single entry", async () => { + const compiler = webpack({ ...config, entry: entryFirst }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with single array entry", async () => { + const compiler = webpack({ ...config, entry: [entryFirst, entrySecond] }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with object entry", async () => { + const compiler = webpack({ + ...config, + entry: { + main: { import: entryFirst } + } + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with dynamic entry", async () => { + const compiler = webpack({ ...config, entry: () => entryFirst }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with dynamic async entry", async () => { + const compiler = webpack({ + ...config, + entry: () => new Promise(resolve => resolve([entryFirst])) + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with multiple entries", async () => { + const compiler = webpack({ + ...config, + entry: { + foo: entryFirst, + bar: entrySecond + }, + optimization: { + runtimeChunk: { + name: "runtime" + } + } + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/test.html`, { + waitUntil: "networkidle0" + }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/runtime.js` }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/foo.js` }); + await waitForConsoleLogFinished(consoleMessages); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with multiple entries #2", async () => { + const compiler = webpack({ + ...config, + entry: { + foo: entryFirst, + bar: entrySecond + }, + optimization: { + runtimeChunk: { + name: "runtime" + } + } + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/test.html`, { + waitUntil: "networkidle0" + }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/runtime.js` }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/bar.js` }); + await waitForConsoleLogFinished(consoleMessages); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should work with multiple entries and "dependOn"', async () => { + const compiler = webpack({ + ...config, + entry: { + foo: { + import: entryFirst, + dependOn: "bar" + }, + bar: entrySecond + } + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/test.html`, { + waitUntil: "networkidle0" + }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/bar.js` }); + await page.addScriptTag({ url: `http://127.0.0.1:${port}/foo.js` }); + await waitForConsoleLogFinished(consoleMessages); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should work with empty", async () => { + const compiler = webpack({ + ...config, + entry: {} + }); + + new webpack.EntryPlugin(compiler.context, entryFirst, { + name: "main" + }).apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/headers.test.js b/packages/rspack-dev-server/tests/e2e/headers.test.js new file mode 100644 index 00000000000..aaea931d29d --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/headers.test.js @@ -0,0 +1,458 @@ +"use strict"; + +const request = require("supertest"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["headers-option"]; + +describe("headers option", () => { + describe("as a string", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: { "X-Foo": "dev-server headers" }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-foo"]).toMatchSnapshot( + "response headers x-foo" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as an array of objects", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: [ + { + key: "X-Foo", + value: "value1" + }, + { + key: "X-Bar", + value: "value2" + } + ], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-foo"]).toMatchSnapshot( + "response headers x-foo" + ); + + expect(response.headers()["x-bar"]).toMatchSnapshot( + "response headers x-bar" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as an array", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: { "X-Bar": ["key1=value1", "key2=value2"] }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers as an array", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-bar"]).toMatchSnapshot( + "response headers x-bar" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as a function", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: () => { + return { "X-Bar": ["key1=value1", "key2=value2"] }; + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers as a function", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-bar"]).toMatchSnapshot( + "response headers x-bar" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as a function returning an array", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: () => [ + { + key: "X-Foo", + value: "value1" + }, + { + key: "X-Bar", + value: "value2" + } + ], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-foo"]).toMatchSnapshot( + "response headers x-foo" + ); + + expect(response.headers()["x-bar"]).toMatchSnapshot( + "response headers x-bar" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("dev middleware headers take precedence for dev middleware output files", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: { "X-Foo": "dev-server-headers" }, + devMiddleware: { + headers: { "X-Foo": "dev-middleware-headers" } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request with headers as a function", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-foo"]).toMatchSnapshot( + "response headers x-foo" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as a string and support HEAD request", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + let req; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + headers: { "X-Foo": "dev-server headers" }, + port + }, + compiler + ); + + await server.start(); + + req = request(server.app); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle HEAD request with headers", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["x-foo"]).toMatchSnapshot( + "response headers x-foo" + ); + expect(response.status()).toMatchSnapshot("response status"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + + const responseForHead = await req.get(`/`); + + expect(responseForHead.headers["x-foo"]).toBe("dev-server headers"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/history-api-fallback.test.js b/packages/rspack-dev-server/tests/e2e/history-api-fallback.test.js new file mode 100644 index 00000000000..6cf5e7b6e07 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/history-api-fallback.test.js @@ -0,0 +1,687 @@ +"use strict"; + +const path = require("path"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/historyapifallback-config/webpack.config"); +const config2 = require("../fixtures/historyapifallback-2-config/webpack.config"); +const config3 = require("../fixtures/historyapifallback-3-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["history-api-fallback-option"]; + +describe("historyApiFallback option", () => { + describe("as boolean", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + historyApiFallback: true, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as object", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + historyApiFallback: { + index: "/bar.html" + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as object with static", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config2); + + server = new Server( + { + static: path.resolve( + __dirname, + "../fixtures/historyapifallback-2-config" + ), + historyApiFallback: { + index: "/bar.html" + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should prefer static file over historyApiFallback", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/random-file.txt`, + { + waitUntil: "networkidle2" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as object with static set to false", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config3); + + server = new Server( + { + static: false, + historyApiFallback: { + index: "/bar.html" + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("historyApiFallback should work and ignore static content", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/index.html`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as object with static and rewrites", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config2); + + server = new Server( + { + port, + static: path.resolve( + __dirname, + "../fixtures/historyapifallback-2-config" + ), + historyApiFallback: { + rewrites: [ + { + from: /other/, + to: "/other.html" + }, + { + from: /.*/, + to: "/bar.html" + } + ] + } + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("historyApiFallback respect rewrites for index", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("historyApiFallback respect rewrites and shows index for unknown urls", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/acme`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("historyApiFallback respect any other specified rewrites", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/other`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe('as object with the "verbose" option', () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + let consoleSpy; + + beforeEach(async () => { + consoleSpy = jest.spyOn(global.console, "log"); + + compiler = webpack(config); + + server = new Server( + { + historyApiFallback: { + index: "/bar.html", + verbose: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + consoleSpy.mockRestore(); + await browser.close(); + await server.stop(); + }); + + it("request to directory and log", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleSpy).toHaveBeenCalledWith( + "Rewriting", + "GET", + "/foo", + "to", + "/bar.html" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe('as object with the "logger" option', () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + let consoleSpy; + + beforeEach(async () => { + consoleSpy = jest.spyOn(global.console, "log"); + + compiler = webpack(config); + + server = new Server( + { + historyApiFallback: { + index: "/bar.html", + logger: consoleSpy + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + consoleSpy.mockRestore(); + await browser.close(); + await server.stop(); + }); + + it("request to directory and log", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleSpy).toHaveBeenCalledWith( + "Rewriting", + "GET", + "/foo", + "to", + "/bar.html" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("in-memory files", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config3); + + server = new Server( + { + static: path.resolve( + __dirname, + "../fixtures/historyapifallback-3-config" + ), + historyApiFallback: true, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should take precedence over static files", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should perform HEAD request in same way as GET", async () => { + await page.goto(`http://127.0.0.1:${port}/foo`, { + waitUntil: "networkidle0" + }); + + const responseGet = await page.evaluate(async () => { + const response = await fetch("/foo", { method: "GET" }); + + return { + contentType: response.headers.get("content-type"), + statusText: response.statusText, + text: await response.text() + }; + }); + + expect(responseGet.contentType).toMatchSnapshot( + "response headers content-type" + ); + + expect(responseGet.statusText).toMatchSnapshot("response status"); + + expect(responseGet.text).toMatchSnapshot("response text"); + + const responseHead = await page.evaluate(async () => { + const response = await fetch("/foo", { method: "HEAD" }); + + return { + contentType: response.headers.get("content-type"), + statusText: response.statusText, + text: await response.text() + }; + }); + + expect(responseHead).toMatchObject({ + ...responseGet, + // HEAD response has an empty body + text: "" + }); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/fixtures/client-config/bar.js b/packages/rspack-dev-server/tests/fixtures/client-config/bar.js new file mode 100644 index 00000000000..1ae523febc6 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/client-config/bar.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Bar."); diff --git a/packages/rspack-dev-server/tests/fixtures/client-config/foo.js b/packages/rspack-dev-server/tests/fixtures/client-config/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/client-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/client-config/index.html b/packages/rspack-dev-server/tests/fixtures/client-config/index.html new file mode 100644 index 00000000000..5b1eef45117 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/client-config/index.html @@ -0,0 +1 @@ + diff --git a/packages/rspack-dev-server/tests/fixtures/client-config/static/foo.txt b/packages/rspack-dev-server/tests/fixtures/client-config/static/foo.txt new file mode 100644 index 00000000000..4add785dafe --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/client-config/static/foo.txt @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/client-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/client-config/webpack.config.js new file mode 100644 index 00000000000..7bbb8522b31 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/client-config/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + devtool: false, + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/bar.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/bar.html new file mode 100644 index 00000000000..d0e33400779 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/bar.html @@ -0,0 +1 @@ +Foobar diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/foo.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/other.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/other.html new file mode 100644 index 00000000000..d7334729422 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/other.html @@ -0,0 +1 @@ +Other file diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/random-file.txt b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/random-file.txt new file mode 100644 index 00000000000..c7ab7e7ea3f --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/random-file.txt @@ -0,0 +1 @@ +Random file diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/webpack.config.js new file mode 100644 index 00000000000..65cf2bfbc11 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-2-config/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/bar.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/bar.html new file mode 100644 index 00000000000..5dee24d5b36 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/bar.html @@ -0,0 +1 @@ +In-memory file diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/foo.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/foo.js new file mode 100644 index 00000000000..aae7eee3a77 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/foo.js @@ -0,0 +1,5 @@ +"use strict"; + +require("./bar.html"); + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/index.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/index.html new file mode 100644 index 00000000000..8824844272e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/index.html @@ -0,0 +1 @@ +static file diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/webpack.config.js new file mode 100644 index 00000000000..72825cb8099 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-3-config/webpack.config.js @@ -0,0 +1,29 @@ +"use strict"; + +const moduleRuleForHTML = { + test: /\.html$/, + type: "asset/resource", + generator: { + filename: "index.html" + } +}; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + module: { + rules: [ + { + ...moduleRuleForHTML + } + ] + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/bar.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/bar.html new file mode 100644 index 00000000000..d0e33400779 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/bar.html @@ -0,0 +1 @@ +Foobar diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/foo.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/foo.js new file mode 100644 index 00000000000..fa06808d305 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/foo.js @@ -0,0 +1,6 @@ +"use strict"; + +require("./index.html"); +require("./bar.html"); + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/index.html b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/index.html new file mode 100644 index 00000000000..f4defc9b2b5 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/index.html @@ -0,0 +1 @@ +Heyyy diff --git a/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/webpack.config.js new file mode 100644 index 00000000000..25ab1d4f719 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/historyapifallback-config/webpack.config.js @@ -0,0 +1,29 @@ +"use strict"; + +const moduleRuleForHTML = { + test: /\.html$/, + type: "asset/resource", + generator: { + filename: "[name][ext]" + } +}; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + module: { + rules: [ + { + ...moduleRuleForHTML + } + ] + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/one.js b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/one.js new file mode 100644 index 00000000000..3141da7ce02 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/one.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("One."); diff --git a/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/two.js b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/two.js new file mode 100644 index 00000000000..0f719c90999 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/two.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Two."); diff --git a/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/webpack.config.js new file mode 100644 index 00000000000..f3d3055f9fc --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-multiple-entries/webpack.config.js @@ -0,0 +1,77 @@ +"use strict"; + +const oneHTMLContent = ` + + + + + test + + + + +`; +const twoHTMLContent = ` + + + + + test + + + + +`; + +module.exports = { + devtool: false, + mode: "development", + context: __dirname, + stats: "none", + entry: { + one: "./one.js", + two: "./two.js" + }, + output: { + path: "/" + }, + experiments: { + lazyCompilation: true + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [ + { + apply(compiler) { + const pluginName = "html-generator-plugin-test"; + const oneFilename = "test-one.html"; + const twoFilename = "test-two.html"; + + compiler.hooks.thisCompilation.tap(pluginName, compilation => { + const { RawSource } = compiler.webpack.sources; + + compilation.hooks.processAssets.tap( + { + name: pluginName, + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const oneSource = new RawSource(oneHTMLContent); + + compilation.emitAsset(oneFilename, oneSource); + + const twoSource = new RawSource(twoHTMLContent); + + compilation.emitAsset(twoFilename, twoSource); + } + ); + }); + } + } + ] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/entry.js b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/entry.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/entry.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/webpack.config.js new file mode 100644 index 00000000000..bd5a5025fb1 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/lazy-compilation-single-entry/webpack.config.js @@ -0,0 +1,58 @@ +"use strict"; + +const HTMLContent = ` + + + + + test + + + + +`; + +module.exports = { + devtool: false, + mode: "development", + context: __dirname, + stats: "none", + entry: "./entry.js", + output: { + path: "/" + }, + experiments: { + lazyCompilation: true + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [ + { + apply(compiler) { + const pluginName = "html-generator-plugin-test"; + const filename = "test.html"; + + compiler.hooks.thisCompilation.tap(pluginName, compilation => { + const { RawSource } = compiler.webpack.sources; + + compilation.hooks.processAssets.tap( + { + name: pluginName, + stage: + compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const source = new RawSource(HTMLContent); + + compilation.emitAsset(filename, source); + } + ); + }); + } + } + ] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/foo.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/webpack.config.js new file mode 100644 index 00000000000..11e40ee4d5d --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration/webpack.config.js @@ -0,0 +1,24 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = [ + { + target: "web", + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] + } +]; diff --git a/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/bar.js b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/bar.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/bar.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/baz.js b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/baz.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/baz.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/foo.js b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/foo.js new file mode 100644 index 00000000000..d2f0aa53ddf --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +require("./test.html"); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/test.html b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/test.html new file mode 100644 index 00000000000..ce013625030 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/test.html @@ -0,0 +1 @@ +hello diff --git a/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/webpack.config.js new file mode 100644 index 00000000000..d7ae8585cb4 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-public-path-config/webpack.config.js @@ -0,0 +1,62 @@ +"use strict"; + +const path = require("path"); + +const moduleRuleForHTML = { + test: /\.html$/, + type: "asset/resource", + generator: { + filename: "path/to/file.html" + } +}; + +module.exports = [ + { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: __dirname, + filename: "foo.js", + publicPath: "/bundle1/" + }, + infrastructureLogging: { + level: "warn" + }, + module: { + rules: [ + { + ...moduleRuleForHTML + } + ] + } + }, + { + mode: "development", + context: __dirname, + stats: "none", + entry: "./bar.js", + output: { + path: path.join(__dirname, "named"), + filename: "bar.js", + publicPath: "/bundle2/" + }, + name: "named", + infrastructureLogging: { + level: "warn" + } + }, + { + mode: "development", + context: __dirname, + entry: "./bar.js", + output: { + path: path.join(__dirname, "dist"), + filename: "bar.js", + publicPath: "auto" + }, + name: "other", + stats: false + } +]; diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/foo.js b/packages/rspack-dev-server/tests/fixtures/reload-config/foo.js new file mode 100644 index 00000000000..68ad7bddf6e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +require("./main.css"); diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css new file mode 100644 index 00000000000..585e8f34510 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css @@ -0,0 +1 @@ +body { background-color: rgb(0, 0, 255); } \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js new file mode 100644 index 00000000000..58a634a77a4 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js @@ -0,0 +1,31 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + entry: "./foo.js", + stats: "none", + output: { + path: "/" + }, + experiments: { + css: true + }, + module: { + // rules: [ + // { + // test: /\.css$/, + // use: [{ loader: "style-loader" }, { loader: "css-loader" }], + // }, + // ], + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/simple-config-other/foo.js b/packages/rspack-dev-server/tests/fixtures/simple-config-other/foo.js new file mode 100644 index 00000000000..fc6e46027d7 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/simple-config-other/foo.js @@ -0,0 +1,5 @@ +"use strict"; + +console.log( + "Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Very Long Line." +); diff --git a/packages/rspack-dev-server/tests/fixtures/simple-config-other/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/simple-config-other/webpack.config.js new file mode 100644 index 00000000000..52a66fb467e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/simple-config-other/webpack.config.js @@ -0,0 +1,15 @@ +"use strict"; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/simple-config/foo.js b/packages/rspack-dev-server/tests/fixtures/simple-config/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/simple-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/simple-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/simple-config/webpack.config.js new file mode 100644 index 00000000000..661a14db435 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/simple-config/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/helpers/get-port.js b/packages/rspack-dev-server/tests/helpers/get-port.js new file mode 100644 index 00000000000..05898a793de --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/get-port.js @@ -0,0 +1,133 @@ +"use strict"; + +/* + * Based on the packages get-port https://www.npmjs.com/package/get-port + * and portfinder https://www.npmjs.com/package/portfinder + * The code structure is similar to get-port, but it searches + * ports deterministically like portfinder + */ +const net = require("net"); +const os = require("os"); + +const minPort = 1024; +const maxPort = 65_535; + +/** + * @return {Set} + */ +const getLocalHosts = () => { + const interfaces = os.networkInterfaces(); + + // Add undefined value for createServer function to use default host, + // and default IPv4 host in case createServer defaults to IPv6. + // eslint-disable-next-line no-undefined + const results = new Set([undefined, "0.0.0.0"]); + + for (const _interface of Object.values(interfaces)) { + if (_interface) { + for (const config of _interface) { + results.add(config.address); + } + } + } + + return results; +}; + +/** + * @param {number} basePort + * @param {string | undefined} host + * @return {Promise} + */ +const checkAvailablePort = (basePort, host) => + new Promise((resolve, reject) => { + const server = net.createServer(); + server.unref(); + server.on("error", reject); + + server.listen(basePort, host, () => { + // Next line should return AddressInfo because we're calling it after listen() and before close() + const { port } = /** @type {import("net").AddressInfo} */ ( + server.address() + ); + server.close(() => { + resolve(port); + }); + }); + }); + +/** + * @param {number} port + * @param {Set} hosts + * @return {Promise} + */ +const getAvailablePort = async (port, hosts) => { + /** + * Errors that mean that host is not available. + * @type {Set} + */ + const nonExistentInterfaceErrors = new Set(["EADDRNOTAVAIL", "EINVAL"]); + /* Check if the post is available on every local host name */ + for (const host of hosts) { + try { + await checkAvailablePort(port, host); // eslint-disable-line no-await-in-loop + } catch (error) { + /* We throw an error only if the interface exists */ + if ( + !nonExistentInterfaceErrors.has( + /** @type {NodeJS.ErrnoException} */ (error).code + ) + ) { + throw error; + } + } + } + + return port; +}; + +/** + * @param {number} basePort + * @param {string=} host + * @return {Promise} + */ +async function getPorts(basePort, host) { + if (basePort < minPort || basePort > maxPort) { + throw new Error(`Port number must lie between ${minPort} and ${maxPort}`); + } + + let port = basePort; + const localhosts = getLocalHosts(); + let hosts; + if (host && !localhosts.has(host)) { + hosts = new Set([host]); + } else { + /* If the host is equivalent to localhost + we need to check every equivalent host + else the port might falsely appear as available + on some operating systems */ + hosts = localhosts; + } + /** @type {Set} */ + const portUnavailableErrors = new Set(["EADDRINUSE", "EACCES"]); + while (port <= maxPort) { + try { + const availablePort = await getAvailablePort(port, hosts); // eslint-disable-line no-await-in-loop + return availablePort; + } catch (error) { + /* Try next port if port is busy; throw for any other error */ + if ( + !portUnavailableErrors.has( + /** @type {NodeJS.ErrnoException} */ (error).code + ) + ) { + throw error; + } + port += 1; + } + } + + throw new Error("No available ports found"); +} + +module.exports = getPorts; diff --git a/packages/rspack-dev-server/tests/helpers/global-setup-test.js b/packages/rspack-dev-server/tests/helpers/global-setup-test.js new file mode 100644 index 00000000000..744d26da6f4 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/global-setup-test.js @@ -0,0 +1,38 @@ +"use strict"; + +// eslint-disable-next-line import/no-extraneous-dependencies +const tcpPortUsed = require("tcp-port-used"); +const { webpackVersion } = require("@rspack/core/package.json"); +const ports = require("./ports-map"); + +// eslint-disable-next-line no-console +console.log(`\n Running tests for webpack @${webpackVersion} \n`); + +async function validatePorts() { + const samples = []; + + Object.keys(ports).forEach(key => { + const value = ports[key]; + const arr = Array.isArray(value) ? value : [value]; + + arr.forEach(port => { + const check = tcpPortUsed.check(port, "localhost").then(inUse => { + if (inUse) { + throw new Error(`${port} has already used. [${key}]`); + } + }); + + samples.push(check); + }); + }); + + try { + await Promise.all(samples); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + process.exit(1); + } +} + +module.exports = validatePorts; diff --git a/packages/rspack-dev-server/tests/helpers/ports-map.js b/packages/rspack-dev-server/tests/helpers/ports-map.js new file mode 100644 index 00000000000..9303e57ef00 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/ports-map.js @@ -0,0 +1,119 @@ +"use strict"; + +// important: new port mappings must be added to the bottom of this list +const listOfTests = { + // CLI tests + "cli-basic": 1, + "cli-port-option": 1, + // e2e tests + bundle: 1, + "sockjs-client": 1, + "web-socket-client": 1, + "hot-and-live-reload": 1, + logging: 1, + overlay: 1, + progress: 1, + "server-and-client-transport": 1, + "web-socket-server-url": 2, + // integration tests + "module-federation": 1, + "multi-compiler": 1, + // unit tests + bonjour: 1, + "client-option": 1, + "compress-option": 1, + "headers-option": 1, + "history-api-fallback-option": 1, + "host-option": 1, + "hot-option": 1, + "http2-option": 1, + "https-option": 1, + "mime-types-option": 1, + "magic-html-option": 1, + "on-after-setup-middleware-option": 1, + "on-before-setup-middleware-option": 1, + "on-listening-option": 1, + "open-option": 1, + "port-option": 1, + "proxy-option": 4, + server: 1, + "setup-exit-signals-option": 1, + "static-directory-option": 1, + "static-public-path-option": 1, + "stats-option": 1, + "watch-files-option": 1, + "web-socket-server-option": 1, + "sockjs-server": 1, + "web-socket-server": 1, + routes: 1, + "web-socket-communication": 1, + ipc: 1, + stats: 1, + "cli-allowed-hosts": 1, + "cli-bonjour": 1, + "cli-client": 1, + "cli-colors": 1, + "cli-compress": 1, + "cli-history-api-fallback": 1, + "cli-host": 1, + "cli-hot": 1, + "cli-http2": 1, + "cli-https": 1, + "cli-live-reload": 1, + "cli-magic-html": 1, + "cli-open": 1, + "cli-static": 1, + "cli-watch-files": 1, + "cli-web-socket-server": 1, + target: 1, + entry: 1, + "allowed-hosts": 2, + host: 1, + api: 1, + "lazy-compilation": 1, + "range-header": 1, + port: 1, + "web-socket-server-test": 1, + "client-reconnect-option": 1, + "cli-server": 1, + "server-option": 1, + "normalize-option": 1, + "setup-middlewares-option": 1, + "options-request-response": 2, + app: 1 +}; + +let startPort = 8089; + +const ports = {}; + +Object.keys(listOfTests).forEach(key => { + const value = listOfTests[key]; + + ports[key] = + value === 1 + ? (startPort += 1) + : [...new Array(value)].map(() => (startPort += 1)); +}); + +const busy = {}; + +module.exports = new Proxy(ports, { + get(target, name) { + if (!target[name]) { + throw new Error( + `Requested "${name}" port(s) for tests not found, please update "test/ports-map.js".` + ); + } + + if (busy[name]) { + throw new Error( + `The "${name}" port is already in use in another test, please add a new one.` + ); + } + + busy[name] = true; + + return target[name]; + } +}); diff --git a/packages/rspack-dev-server/tests/helpers/puppeteer-constants.js b/packages/rspack-dev-server/tests/helpers/puppeteer-constants.js new file mode 100644 index 00000000000..4c8f3d8eacd --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/puppeteer-constants.js @@ -0,0 +1,53 @@ +"use strict"; + +module.exports = { + reloadReadyDelay: 5000, + completeReloadDelay: 10000, + initConsoleDelay: 3000, + awaitServerCloseDelay: 1000, + beforeBrowserCloseDelay: 3000, + puppeteerArgs: [ + "--disable-background-timer-throttling", + "--disable-breakpad", + "--disable-client-side-phishing-detection", + "--disable-cloud-import", + "--disable-default-apps", + "--disable-dev-shm-usage", + "--disable-extensions", + "--disable-gesture-typing", + "--disable-hang-monitor", + "--disable-infobars", + "--disable-notifications", + "--disable-offer-store-unmasked-wallet-cards", + "--disable-offer-upload-credit-cards", + "--disable-popup-blocking", + "--disable-print-preview", + "--disable-prompt-on-repost", + "--disable-setuid-sandbox", + "--disable-speech-api", + "--disable-sync", + "--disable-tab-for-desktop-share", + "--disable-translate", + "--disable-voice-input", + "--disable-wake-on-wifi", + "--enable-async-dns", + "--enable-simple-cache-backend", + "--enable-tcp-fast-open", + "--enable-webgl", + "--hide-scrollbars", + "--ignore-gpu-blacklist", + "--media-cache-size=33554432", + "--metrics-recording-only", + "--mute-audio", + "--no-default-browser-check", + "--no-first-run", + "--no-pings", + "--no-sandbox", + "--no-zygote", + "--password-store=basic", + "--prerender-from-omnibox=disabled", + "--use-gl=swiftshader", + "--use-mock-keychain", + "--disable-field-trial-config" + ] +}; diff --git a/packages/rspack-dev-server/tests/helpers/run-browser.js b/packages/rspack-dev-server/tests/helpers/run-browser.js new file mode 100644 index 00000000000..91948e023fd --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/run-browser.js @@ -0,0 +1,94 @@ +"use strict"; + +const puppeteer = require("puppeteer"); +const { puppeteerArgs } = require("./puppeteer-constants"); + +/** + * @typedef {Object} RunBrowserResult + * @property {import('puppeteer').Page} page + * @property {import('puppeteer').Browser} browser + */ + +/** + * @param {Parameters[0]} config + * @returns {Promise} + */ +function runBrowser(config) { + return new Promise((resolve, reject) => { + /** + * @type {import('puppeteer').Page} + */ + let page; + /** + * @type {import('puppeteer').Browser} + */ + let browser; + + puppeteer + .launch({ + headless: "new", + // because of invalid localhost certificate + acceptInsecureCerts: true, + // args come from: https://github.com/alixaxel/chrome-aws-lambda/blob/master/source/index.js + args: puppeteerArgs + }) + .then(launchedBrowser => { + browser = launchedBrowser; + + return runPage(launchedBrowser, config); + }) + .then(newPage => { + page = newPage; + + resolve({ page, browser }); + }) + .catch(reject); + }); +} + +function runPage(browser, config) { + /** + * @type {import('puppeteer').Page} + */ + let page; + + const options = { + viewport: { + width: 500, + height: 500 + }, + userAgent: "", + ...config + }; + + return Promise.resolve() + .then(() => browser.newPage()) + .then(newPage => { + page = newPage; + page.emulate(options); + + return page.setRequestInterception(true); + }) + .then(() => { + page.on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + if (interceptedRequest.url().includes("favicon.ico")) { + interceptedRequest.respond({ + status: 200, + contentType: "image/png", + body: "Empty" + }); + } else { + interceptedRequest.continue( + interceptedRequest.continueRequestOverrides(), + 10 + ); + } + }); + + return page; + }); +} + +module.exports = runBrowser; +module.exports.runPage = runPage; diff --git a/packages/rspack-dev-server/tests/helpers/sequencer.js b/packages/rspack-dev-server/tests/helpers/sequencer.js new file mode 100644 index 00000000000..b813fa33208 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/sequencer.js @@ -0,0 +1,10 @@ +const Sequencer = require("@jest/test-sequencer").default; + +class NamedSequencer extends Sequencer { + sort(tests) { + const copyTests = [...tests]; + return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); + } +} + +module.exports = NamedSequencer; diff --git a/packages/rspack-dev-server/tests/helpers/session-subscribe.js b/packages/rspack-dev-server/tests/helpers/session-subscribe.js new file mode 100644 index 00000000000..5d079a0eae2 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/session-subscribe.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = async function sessionSubscribe(session) { + session.on("sessionattached", s => { + sessionSubscribe(s); + }); + session.send("Network.enable"); + session.send("Runtime.runIfWaitingForDebugger"); +}; diff --git a/packages/rspack-dev-server/tests/helpers/setup-test.js b/packages/rspack-dev-server/tests/helpers/setup-test.js new file mode 100644 index 00000000000..99a74a59bfe --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/setup-test.js @@ -0,0 +1,5 @@ +"use strict"; + +process.env.CHOKIDAR_USEPOLLING = true; + +jest.setTimeout(400000); diff --git a/packages/rspack-dev-server/tests/helpers/snapshot-resolver.js b/packages/rspack-dev-server/tests/helpers/snapshot-resolver.js new file mode 100644 index 00000000000..0a17d51a6f4 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/snapshot-resolver.js @@ -0,0 +1,25 @@ +"use strict"; + +const path = require("path"); +const webpack = require("webpack"); + +const [webpackVersion] = webpack.version; +const snapshotExtension = `.snap.webpack${webpackVersion}`; + +module.exports = { + resolveSnapshotPath: testPath => + path.join( + path.dirname(testPath), + "__snapshots__", + `${path.basename(testPath)}${snapshotExtension}` + ), + resolveTestPath: snapshotPath => + snapshotPath + .replace(`${path.sep}__snapshots__`, "") + .slice(0, -snapshotExtension.length), + testPathForConsistencyCheck: path.join( + "consistency_check", + "__tests__", + "example.test.js" + ) +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4adc158f54..86be8d15562 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -521,6 +521,9 @@ importers: mime-types: specifier: ^2.1.35 version: 2.1.35 + p-retry: + specifier: 4.6.2 + version: 4.6.2 webpack-dev-middleware: specifier: ^7.4.2 version: 7.4.2(webpack@5.94.0(webpack-cli@5.1.4(webpack@5.94.0))) @@ -552,9 +555,27 @@ importers: '@types/ws': specifier: 8.5.10 version: 8.5.10 + graceful-fs: + specifier: 4.2.10 + version: 4.2.10(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q) + http-proxy: + specifier: ^1.18.1 + version: 1.18.1 jest-serializer-path: specifier: ^0.1.15 version: 0.1.15 + puppeteer: + specifier: ^23.2.2 + version: 23.3.0(typescript@5.0.2) + sockjs-client: + specifier: ^1.6.1 + version: 1.6.1 + supertest: + specifier: ^6.1.3 + version: 6.3.4 + tcp-port-used: + specifier: ^1.0.2 + version: 1.0.2 typescript: specifier: 5.0.2 version: 5.0.2 @@ -2763,6 +2784,11 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@puppeteer/browsers@2.4.0': + resolution: {integrity: sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==} + engines: {node: '>=18'} + hasBin: true + '@rollup/rollup-android-arm-eabi@4.17.2': resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} cpu: [arm] @@ -3207,6 +3233,9 @@ packages: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -3709,6 +3738,9 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/retry@0.12.2': resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} @@ -3766,6 +3798,9 @@ packages: '@types/yargs@17.0.32': resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@vercel/ncc@0.38.1': resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} hasBin: true @@ -4127,6 +4162,10 @@ packages: assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -4147,6 +4186,9 @@ packages: b-validate@1.5.3: resolution: {integrity: sha512-iCvCkGFskbaYtfQ0a3GmcQCHl/Sv1GufXFGuUQ+FE+WJa7A/espLOuFIn09B944V8/ImPj71T4+rTASxO2PAuA==} + b4a@1.6.6: + resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4215,9 +4257,28 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.4.2: + resolution: {integrity: sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==} + + bare-fs@2.3.3: + resolution: {integrity: sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==} + + bare-os@2.4.2: + resolution: {integrity: sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==} + + bare-path@2.1.3: + resolution: {integrity: sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==} + + bare-stream@2.2.1: + resolution: {integrity: sha512-YTB47kHwBW9zSG8LD77MIBAAQXjU2WjAkMHeeb7hUplVs6+IoM5I7uEVQNPMB7lj9r8I76UMdoMkGnCodHOLqg==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + basic-ftp@5.0.5: + resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} + engines: {node: '>=10.0.0'} + batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -4318,6 +4379,9 @@ packages: buffer-builder@0.2.0: resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -4464,6 +4528,11 @@ packages: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} + chromium-bidi@0.6.5: + resolution: {integrity: sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==} + peerDependencies: + devtools-protocol: '*' + ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -4640,6 +4709,9 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -4713,6 +4785,9 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} @@ -4961,6 +5036,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -4996,6 +5075,23 @@ packages: supports-color: optional: true + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.1: + resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -5005,6 +5101,15 @@ packages: supports-color: optional: true + debug@4.3.6: + resolution: {integrity: sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -5036,6 +5141,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -5073,6 +5181,10 @@ packages: defined@1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -5123,6 +5235,12 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + devtools-protocol@0.0.1330662: + resolution: {integrity: sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw==} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -5419,6 +5537,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource@2.0.2: + resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} + engines: {node: '>=12.0.0'} + evp_bytestokey@1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -5453,9 +5575,17 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -5489,6 +5619,9 @@ packages: fbjs@3.0.5: resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -5591,6 +5724,9 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + formidable@2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -5697,6 +5833,10 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + get-uri@6.0.3: + resolution: {integrity: sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==} + engines: {node: '>= 14'} + gl-matrix@3.4.3: resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==} @@ -5830,6 +5970,10 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + hexoid@1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + history@4.10.1: resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==} @@ -5947,6 +6091,10 @@ packages: resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} engines: {node: '>= 14'} + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -6072,6 +6220,10 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + ip-regex@4.3.0: resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} engines: {node: '>=8'} @@ -6298,6 +6450,9 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -6319,6 +6474,10 @@ packages: is-yarn-global@0.3.0: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} + is2@2.0.9: + resolution: {integrity: sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g==} + engines: {node: '>=v0.10.0'} + isarray@0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} @@ -6539,6 +6698,9 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsdom@20.0.3: resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} engines: {node: '>=14'} @@ -6801,6 +6963,10 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + magic-string@0.30.9: resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} engines: {node: '>=12'} @@ -6905,6 +7071,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -6958,6 +7129,9 @@ packages: resolution: {integrity: sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==} engines: {node: '>=4.0.0'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -7040,6 +7214,10 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + netmask@2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} @@ -7244,6 +7422,10 @@ packages: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + p-retry@6.2.0: resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} engines: {node: '>=16.17'} @@ -7256,6 +7438,14 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + pac-proxy-agent@7.0.2: + resolution: {integrity: sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} engines: {node: '>=8'} @@ -7383,6 +7573,9 @@ packages: pdfast@0.2.0: resolution: {integrity: sha512-cq6TTu6qKSFUHwEahi68k/kqN2mfepjkGrG9Un70cgdRRKLKY6Rf8P8uvP2NvZktaQZNF3YE7agEkLj0vGK9bA==} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -7581,6 +7774,10 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: @@ -7606,6 +7803,13 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-agent@6.4.0: + resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} @@ -7681,6 +7885,15 @@ packages: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} + puppeteer-core@23.3.0: + resolution: {integrity: sha512-sB2SsVMFs4gKad5OCdv6w5vocvtEUrRl0zQqSyRPbo/cj1Ktbarmhxy02Zyb9R9HrssBcJDZbkrvBnbaesPyYg==} + engines: {node: '>=18'} + + puppeteer@23.3.0: + resolution: {integrity: sha512-e2jY8cdWSUGsrLxqGm3hIbJq/UIk1uOY8XY7SM51leXkH7shrIyE91lK90Q9byX6tte+cyL3HKqlWBEd6TjWTA==} + engines: {node: '>=18'} + hasBin: true + pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} @@ -7710,6 +7923,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + quick-lru@4.0.1: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} @@ -8343,6 +8559,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -8481,12 +8702,28 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + sockjs-client@1.6.1: + resolution: {integrity: sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==} + engines: {node: '>=12'} + sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + socks-proxy-agent@8.0.4: + resolution: {integrity: sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==} + engines: {node: '>= 14'} + + socks@2.8.3: + resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sort-keys@4.2.0: resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} engines: {node: '>=8'} @@ -8549,6 +8786,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + ssri@6.0.2: resolution: {integrity: sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==} @@ -8594,6 +8834,9 @@ packages: stream-shift@1.0.3: resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + streamx@2.20.0: + resolution: {integrity: sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -8689,10 +8932,19 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + superagent@8.1.2: + resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} + engines: {node: '>=6.4.0 <13 || >=14'} + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + superstruct@1.0.4: resolution: {integrity: sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==} engines: {node: '>=14.0.0'} + supertest@6.3.4: + resolution: {integrity: sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==} + engines: {node: '>=6.4.0'} + supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -8747,6 +8999,15 @@ packages: resolution: {integrity: sha512-KCuXjYxCZ3ru40dmND+oCLsXyuA8hoseu2SS404Px5ouyS0A99v8X/mdiLqsR5MTAyamMBN7PRwt2Dv3+xGIxw==} hasBin: true + tar-fs@3.0.6: + resolution: {integrity: sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==} + + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + + tcp-port-used@1.0.2: + resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -8777,6 +9038,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-decoder@1.1.1: + resolution: {integrity: sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -8991,6 +9255,9 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} + typed-query-selector@2.12.0: + resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} + typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} @@ -9026,6 +9293,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbzip2-stream@1.4.3: + resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -9104,6 +9374,9 @@ packages: url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} + urlpattern-polyfill@10.0.0: + resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + use-callback-ref@1.3.2: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} @@ -9464,6 +9737,18 @@ packages: utf-8-validate: optional: true + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xdg-basedir@4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} @@ -9520,9 +9805,16 @@ packages: resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} engines: {node: '>=12'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yargs@3.10.0: resolution: {integrity: sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -9872,7 +10164,7 @@ snapshots: '@babel/core': 7.24.4 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.4 + debug: 4.3.6 lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -10755,7 +11047,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.4 + debug: 4.3.6 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11639,6 +11931,19 @@ snapshots: '@polka/url@1.0.0-next.25': {} + '@puppeteer/browsers@2.4.0': + dependencies: + debug: 4.3.6 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.4.0 + semver: 7.6.3 + tar-fs: 3.0.6 + unbzip2-stream: 1.4.3 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + '@rollup/rollup-android-arm-eabi@4.17.2': optional: true @@ -12072,6 +12377,8 @@ snapshots: '@tootallnate/once@2.0.0': {} + '@tootallnate/quickjs-emscripten@0.23.0': {} + '@trysound/sax@0.2.0': {} '@tsconfig/node10@1.0.11': {} @@ -13107,6 +13414,8 @@ snapshots: dependencies: '@types/node': 20.12.7 + '@types/retry@0.12.0': {} + '@types/retry@0.12.2': {} '@types/rimraf@3.0.2': @@ -13190,6 +13499,11 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.12.7 + optional: true + '@vercel/ncc@0.38.1': {} '@vue/compiler-core@3.4.21': @@ -13388,13 +13702,13 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color agent-base@7.1.1: dependencies: - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -13582,6 +13896,10 @@ snapshots: object.assign: 4.1.5 util: 0.12.5 + ast-types@0.13.4: + dependencies: + tslib: 2.6.2 + astral-regex@2.0.0: {} asynckit@0.4.0: {} @@ -13600,6 +13918,8 @@ snapshots: b-validate@1.5.3: {} + b4a@1.6.6: {} + babel-jest@29.7.0(@babel/core@7.24.4): dependencies: '@babel/core': 7.24.4 @@ -13722,8 +14042,34 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.4.2: + optional: true + + bare-fs@2.3.3: + dependencies: + bare-events: 2.4.2 + bare-path: 2.1.3 + bare-stream: 2.2.1 + optional: true + + bare-os@2.4.2: + optional: true + + bare-path@2.1.3: + dependencies: + bare-os: 2.4.2 + optional: true + + bare-stream@2.2.1: + dependencies: + b4a: 1.6.6 + streamx: 2.20.0 + optional: true + base64-js@1.5.1: {} + basic-ftp@5.0.5: {} + batch@0.6.1: {} before-after-hook@2.2.3: {} @@ -13894,6 +14240,8 @@ snapshots: buffer-builder@0.2.0: {} + buffer-crc32@0.2.13: {} + buffer-from@1.1.2: {} buffer-xor@1.0.3: {} @@ -14083,6 +14431,13 @@ snapshots: chrome-trace-event@1.0.3: {} + chromium-bidi@0.6.5(devtools-protocol@0.0.1330662): + dependencies: + devtools-protocol: 0.0.1330662 + mitt: 3.0.1 + urlpattern-polyfill: 10.0.0 + zod: 3.23.8 + ci-info@2.0.0: {} ci-info@3.9.0: {} @@ -14248,6 +14603,8 @@ snapshots: commondir@1.0.1: {} + component-emitter@1.3.1: {} + compressible@2.0.18: dependencies: mime-db: 1.52.0 @@ -14334,6 +14691,8 @@ snapshots: cookie@0.6.0: {} + cookiejar@2.1.4: {} + copy-anything@2.0.6: dependencies: is-what: 3.14.1 @@ -14703,6 +15062,8 @@ snapshots: data-uri-to-buffer@4.0.1: {} + data-uri-to-buffer@6.0.2: {} + data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -14742,10 +15103,22 @@ snapshots: dependencies: ms: 2.0.0 + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.3.1: + dependencies: + ms: 2.1.2 + debug@4.3.4: dependencies: ms: 2.1.2 + debug@4.3.6: + dependencies: + ms: 2.1.2 + decamelize@1.2.0: {} decimal.js@10.4.3: {} @@ -14769,6 +15142,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} default-browser-id@5.0.0: {} @@ -14804,6 +15179,12 @@ snapshots: defined@1.0.1: {} + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + del@6.1.1: dependencies: globby: 11.1.0 @@ -14844,6 +15225,13 @@ snapshots: detect-node@2.1.0: {} + devtools-protocol@0.0.1330662: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + didyoumean@1.2.2: {} diff-sequences@29.6.3: {} @@ -15187,6 +15575,8 @@ snapshots: events@3.3.0: {} + eventsource@2.0.2: {} + evp_bytestokey@1.0.3: dependencies: md5.js: 1.3.5 @@ -15274,8 +15664,20 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + extract-zip@2.0.1: + dependencies: + debug: 4.3.6 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -15320,6 +15722,10 @@ snapshots: transitivePeerDependencies: - encoding + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fecha@4.2.3: {} fetch-blob@3.2.0: @@ -15442,6 +15848,13 @@ snapshots: dependencies: fetch-blob: 3.2.0 + formidable@2.1.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.12.0 + forwarded@0.2.0: {} fresh@0.5.2: {} @@ -15550,6 +15963,15 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.4 + get-uri@6.0.3: + dependencies: + basic-ftp: 5.0.5 + data-uri-to-buffer: 6.0.2 + debug: 4.3.6 + fs-extra: 11.2.0 + transitivePeerDependencies: + - supports-color + gl-matrix@3.4.3: {} glob-parent@3.1.0: @@ -15705,6 +16127,8 @@ snapshots: he@1.2.0: {} + hexoid@1.0.0: {} + history@4.10.1: dependencies: '@babel/runtime': 7.24.4 @@ -15839,7 +16263,7 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -15875,7 +16299,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.6 transitivePeerDependencies: - supports-color @@ -15886,6 +16310,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.6 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -15996,6 +16427,11 @@ snapshots: dependencies: loose-envify: 1.4.0 + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + ip-regex@4.3.0: {} ipaddr.js@1.9.1: {} @@ -16173,6 +16609,8 @@ snapshots: is-unicode-supported@0.1.0: {} + is-url@1.2.4: {} + is-weakref@1.0.2: dependencies: call-bind: 1.0.7 @@ -16191,6 +16629,12 @@ snapshots: is-yarn-global@0.3.0: {} + is2@2.0.9: + dependencies: + deep-is: 0.1.4 + ip-regex: 4.3.0 + is-url: 1.2.4 + isarray@0.0.1: {} isarray@1.0.0: {} @@ -16219,7 +16663,7 @@ snapshots: '@babel/parser': 7.24.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.6.2 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -16231,7 +16675,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.6 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -16614,6 +17058,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbn@1.1.0: {} + jsdom@20.0.3: dependencies: abab: 2.0.6 @@ -16929,6 +17375,8 @@ snapshots: dependencies: yallist: 4.0.0 + lru-cache@7.18.3: {} + magic-string@0.30.9: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -16944,7 +17392,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.6.2 + semver: 7.6.3 make-error@1.3.6: {} @@ -17017,6 +17465,8 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mimic-fn@2.1.0: {} mimic-fn@3.1.0: {} @@ -17066,6 +17516,8 @@ snapshots: stream-each: 1.2.3 through2: 2.0.5 + mitt@3.0.1: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -17147,6 +17599,8 @@ snapshots: neo-async@2.6.2: {} + netmask@2.0.2: {} + next-tick@1.1.0: {} no-case@3.0.4: @@ -17351,6 +17805,11 @@ snapshots: dependencies: aggregate-error: 3.1.0 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + p-retry@6.2.0: dependencies: '@types/retry': 0.12.2 @@ -17363,6 +17822,24 @@ snapshots: p-try@2.2.0: {} + pac-proxy-agent@7.0.2: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.1 + debug: 4.3.6 + get-uri: 6.0.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.4 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.0.2 + package-json@6.5.0: dependencies: got: 9.6.0 @@ -17483,6 +17960,8 @@ snapshots: pdfast@0.2.0: {} + pend@1.2.0: {} + picocolors@1.0.0: {} picocolors@1.0.1: {} @@ -17666,6 +18145,8 @@ snapshots: process@0.11.10: {} + progress@2.0.3: {} + promise-inflight@1.0.1(bluebird@3.7.2): optionalDependencies: bluebird: 3.7.2 @@ -17692,6 +18173,21 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent@6.4.0: + dependencies: + agent-base: 7.1.1 + debug: 4.3.6 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.4 + lru-cache: 7.18.3 + pac-proxy-agent: 7.0.2 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.4 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + prr@1.0.1: optional: true @@ -17810,6 +18306,33 @@ snapshots: dependencies: escape-goat: 2.1.1 + puppeteer-core@23.3.0: + dependencies: + '@puppeteer/browsers': 2.4.0 + chromium-bidi: 0.6.5(devtools-protocol@0.0.1330662) + debug: 4.3.6 + devtools-protocol: 0.0.1330662 + typed-query-selector: 2.12.0 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + puppeteer@23.3.0(typescript@5.0.2): + dependencies: + '@puppeteer/browsers': 2.4.0 + chromium-bidi: 0.6.5(devtools-protocol@0.0.1330662) + cosmiconfig: 9.0.0(typescript@5.0.2) + devtools-protocol: 0.0.1330662 + puppeteer-core: 23.3.0 + typed-query-selector: 2.12.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + pure-rand@6.1.0: {} qs@6.11.0: @@ -17835,6 +18358,8 @@ snapshots: queue-microtask@1.2.3: {} + queue-tick@1.0.1: {} + quick-lru@4.0.1: {} quickselect@1.1.1: {} @@ -18497,6 +19022,8 @@ snapshots: semver@7.6.2: {} + semver@7.6.3: {} + send@0.18.0: dependencies: debug: 2.6.9 @@ -18680,17 +19207,42 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + smart-buffer@4.2.0: {} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.6.2 + sockjs-client@1.6.1: + dependencies: + debug: 3.2.7 + eventsource: 2.0.2 + faye-websocket: 0.11.4 + inherits: 2.0.4 + url-parse: 1.5.10 + transitivePeerDependencies: + - supports-color + sockjs@0.3.24: dependencies: faye-websocket: 0.11.4 uuid: 8.3.2 websocket-driver: 0.7.4 + socks-proxy-agent@8.0.4: + dependencies: + agent-base: 7.1.1 + debug: 4.3.6 + socks: 2.8.3 + transitivePeerDependencies: + - supports-color + + socks@2.8.3: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + sort-keys@4.2.0: dependencies: is-plain-obj: 2.1.0 @@ -18729,7 +19281,7 @@ snapshots: spdy-transport@3.0.0: dependencies: - debug: 4.3.4 + debug: 4.3.6 detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -18762,6 +19314,8 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.3: {} + ssri@6.0.2: dependencies: figgy-pudding: 3.5.2 @@ -18812,6 +19366,14 @@ snapshots: stream-shift@1.0.3: {} + streamx@2.20.0: + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.1.1 + optionalDependencies: + bare-events: 2.4.2 + strict-uri-encode@2.0.0: {} string-argv@0.3.2: {} @@ -18921,8 +19483,30 @@ snapshots: pirates: 4.0.6 ts-interface-checker: 0.1.13 + superagent@8.1.2: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.6 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.12.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + superstruct@1.0.4: {} + supertest@6.3.4: + dependencies: + methods: 1.1.2 + superagent: 8.1.2 + transitivePeerDependencies: + - supports-color + supports-color@2.0.0: {} supports-color@5.5.0: @@ -19015,6 +19599,27 @@ snapshots: resolve: 1.22.8 string.prototype.trim: 1.2.9 + tar-fs@3.0.6: + dependencies: + pump: 3.0.0 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 2.3.3 + bare-path: 2.1.3 + + tar-stream@3.1.7: + dependencies: + b4a: 1.6.6 + fast-fifo: 1.3.2 + streamx: 2.20.0 + + tcp-port-used@1.0.2: + dependencies: + debug: 4.3.1 + is2: 2.0.9 + transitivePeerDependencies: + - supports-color + terser-webpack-plugin@5.3.10(@swc/core@1.4.0(@swc/helpers@0.5.1))(webpack@5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.1))(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: '@jridgewell/trace-mapping': 0.3.25 @@ -19097,6 +19702,10 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-decoder@1.1.1: + dependencies: + b4a: 1.6.6 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -19357,6 +19966,8 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 + typed-query-selector@2.12.0: {} + typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 @@ -19388,6 +19999,11 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + unbzip2-stream@1.4.3: + dependencies: + buffer: 5.7.1 + through: 2.3.8 + undici-types@5.26.5: {} undici@5.28.4: @@ -19473,6 +20089,8 @@ snapshots: punycode: 1.4.1 qs: 6.12.0 + urlpattern-polyfill@10.0.0: {} + use-callback-ref@1.3.2(@types/react@18.2.75)(react@18.3.1): dependencies: react: 18.3.1 @@ -20154,6 +20772,8 @@ snapshots: ws@8.17.1: {} + ws@8.18.0: {} + xdg-basedir@4.0.0: {} xml-name-validator@4.0.0: {} @@ -20197,6 +20817,16 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yargs@3.10.0: dependencies: camelcase: 1.2.1 @@ -20204,6 +20834,11 @@ snapshots: decamelize: 1.2.0 window-size: 0.1.0 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yn@3.1.1: {} yocto-queue@0.1.0: {} From 5aa2f24d6bd2497eb400903234a12ab9e7981b25 Mon Sep 17 00:00:00 2001 From: Cong-Cong Pan Date: Tue, 10 Sep 2024 13:59:10 +0800 Subject: [PATCH 12/51] fix: duplicate emit source map from child compiler (#7847) --- .../src/mapped_assets_cache.rs | 4 +-- .../src/source_map_dev_tool_plugin.rs | 7 +++- .../require_resolve_context_dependency.rs | 7 ++-- .../child.js | 1 + .../index.js | 4 +++ .../rspack.config.js | 36 +++++++++++++++++++ 6 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/child.js create mode 100644 packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/rspack.config.js diff --git a/crates/rspack_plugin_devtool/src/mapped_assets_cache.rs b/crates/rspack_plugin_devtool/src/mapped_assets_cache.rs index 07d66cd2152..faf5b0c0ef8 100644 --- a/crates/rspack_plugin_devtool/src/mapped_assets_cache.rs +++ b/crates/rspack_plugin_devtool/src/mapped_assets_cache.rs @@ -1,6 +1,6 @@ use dashmap::DashMap; use futures::Future; -use rspack_core::{CompilationAsset, CompilationAssets}; +use rspack_core::CompilationAsset; use rspack_error::{Error, Result}; use crate::MappedAsset; @@ -15,7 +15,7 @@ impl MappedAssetsCache { pub async fn use_cache<'a, F, R>( &self, - assets: &'a CompilationAssets, + assets: Vec<(&'a String, &'a CompilationAsset)>, map_assets: F, ) -> Result> where diff --git a/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs b/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs index f58d8340b9e..37c902fc9ba 100644 --- a/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs +++ b/crates/rspack_plugin_devtool/src/source_map_dev_tool_plugin.rs @@ -506,9 +506,14 @@ async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { } let start = logger.time("collect source maps"); + let raw_assets = compilation + .assets() + .iter() + .filter(|(_filename, asset)| asset.info.related.source_map.is_none()) + .collect::>(); let mapped_asstes = self .mapped_assets_cache - .use_cache(compilation.assets(), |assets| { + .use_cache(raw_assets, |assets| { self.map_assets(compilation, &file_to_chunk, assets) }) .await?; diff --git a/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs index eb9ea08a61b..1dbbbfb1b3c 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/require_resolve_context_dependency.rs @@ -1,8 +1,7 @@ use rspack_core::{ AffectType, AsModuleDependency, Compilation, ContextDependency, ContextOptions, ContextTypePrefix, Dependency, DependencyCategory, DependencyId, DependencyTemplate, - DependencyType, ErrorSpan, RealDependencyLocation, RuntimeSpec, TemplateContext, - TemplateReplaceSource, + DependencyType, RealDependencyLocation, RuntimeSpec, TemplateContext, TemplateReplaceSource, }; use super::{context_dependency_template_as_id, create_resource_identifier_for_context_dependency}; @@ -42,8 +41,8 @@ impl Dependency for RequireResolveContextDependency { &DependencyType::RequireContext } - fn span(&self) -> Option { - Some(ErrorSpan::new(self.range.start, self.range.end)) + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) } fn could_affect_referencing_module(&self) -> AffectType { diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/child.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/child.js new file mode 100644 index 00000000000..bb1843d113a --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/child.js @@ -0,0 +1 @@ +export const foo = 1; diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/index.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/index.js new file mode 100644 index 00000000000..af3cad69add --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/index.js @@ -0,0 +1,4 @@ +it("should successfully compile and retrieve assets from the child compiler", function () { + const child = require('./child'); + expect(child.foo).toBe(1); +}); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/rspack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/rspack.config.js new file mode 100644 index 00000000000..240031a52a7 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/source-map-dev-tool-plugin-child-compiler/rspack.config.js @@ -0,0 +1,36 @@ +const path = require("path"); +const rspack = require("@rspack/core"); + +const CHILD_ID = "child"; +const CHILD_FILENAME = "./child"; + +/** + * @type {import("@rspack/core").Configuration} + */ +module.exports = { + devtool: "source-map", + node: { + __dirname: false, + __filename: false + }, + externals: { + CHILD_FILENAME: `commonjs ${CHILD_FILENAME}` + }, + output: { + filename: "[name].js" + }, + plugins: [ + new rspack.DefinePlugin({ + CONTEXT: JSON.stringify(__dirname) + }), + compiler => { + compiler.hooks.make.tapAsync('PLUGIN', (compilation, callback) => { + const outputOptions = {}; + const childCompiler = compilation.createChildCompiler(CHILD_ID, outputOptions); + const SingleEntryPlugin = compiler.webpack.EntryPlugin; + new SingleEntryPlugin(compiler.context, path.join(__dirname, CHILD_FILENAME), CHILD_ID).apply(childCompiler); + childCompiler.runAsChild(err => callback(err)); + }); + } + ] +}; From be2ee96017eacb59d73c5214b5e2b8e3dd58a179 Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Tue, 10 Sep 2024 14:28:04 +0800 Subject: [PATCH 13/51] chore(infra/biome): enable `noImplicitAnyLet` and `noArrayIndexKey` rules (#7843) * chore(infra/biome): enable noImplicitAny and react/no-array-index-key rules * chore: update api.md --- biome.jsonc | 4 +-- packages/rspack-cli/src/cli.ts | 10 ++---- packages/rspack-dev-server/src/ansiHTML.ts | 2 +- packages/rspack-dev-server/src/middleware.ts | 2 +- .../src/compare/format-code.ts | 4 +-- .../webpack-module-placeholder-plugin.ts | 4 +-- .../src/processor/defaults.ts | 4 +-- .../rspack-test-tools/src/processor/hook.ts | 2 +- packages/rspack/etc/api.md | 32 +++++++++---------- packages/rspack/src/MultiCompiler.ts | 2 +- .../src/builtin-plugin/HtmlRspackPlugin.ts | 6 +++- .../css-extract/hmr/hotModuleReplacement.ts | 2 +- .../src/builtin-plugin/css-extract/loader.ts | 18 +++-------- packages/rspack/src/config/adapterRuleUse.ts | 12 +++---- packages/rspack/src/lib/HookWebpackError.ts | 2 +- .../rspack/src/lib/cache/getLazyHashedEtag.ts | 2 +- .../src/stats/DefaultStatsPrinterPlugin.ts | 2 +- .../rspack/src/stats/statsFactoryUtils.ts | 2 +- packages/rspack/src/util/IterableHelpers.ts | 2 +- .../rspack/src/util/bindingVersionCheck.ts | 2 +- packages/rspack/src/util/cleverMerge.ts | 2 +- packages/rspack/src/util/hash/wasm-hash.ts | 2 +- website/components/builtIns/Table.tsx | 21 ++++++------ 23 files changed, 66 insertions(+), 75 deletions(-) diff --git a/biome.jsonc b/biome.jsonc index 0fa5b7eff3c..6688f9c3bc9 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -52,9 +52,7 @@ "noFallthroughSwitchClause": "off", "noConfusingVoidType": "off", "noPrototypeBuiltins": "off", - "noImplicitAnyLet": "off", "noAssignInExpressions": "off", - "noArrayIndexKey": "off", "noControlCharactersInRegex": "off", "noExplicitAny": "off" } @@ -78,4 +76,4 @@ "clientKind": "git", "useIgnoreFile": true } -} \ No newline at end of file +} diff --git a/packages/rspack-cli/src/cli.ts b/packages/rspack-cli/src/cli.ts index ecb900a8e56..079d1ee5389 100644 --- a/packages/rspack-cli/src/cli.ts +++ b/packages/rspack-cli/src/cli.ts @@ -60,7 +60,7 @@ export class RspackCLI { ? (config as MultiRspackOptions).some(i => i.watch) : (config as RspackOptions).watch; - let compiler; + let compiler: MultiCompiler | Compiler | null; try { compiler = rspack(config, isWatch ? callback : undefined); } catch (e) { @@ -80,13 +80,7 @@ export class RspackCLI { createColors(useColor?: boolean): RspackCLIColors { const { createColors, isColorSupported } = require("colorette"); - let shouldUseColor; - - if (useColor) { - shouldUseColor = useColor; - } else { - shouldUseColor = isColorSupported; - } + const shouldUseColor = useColor || isColorSupported; return { ...createColors({ useColor: shouldUseColor }), diff --git a/packages/rspack-dev-server/src/ansiHTML.ts b/packages/rspack-dev-server/src/ansiHTML.ts index 480a0cc50a2..52094c03caf 100644 --- a/packages/rspack-dev-server/src/ansiHTML.ts +++ b/packages/rspack-dev-server/src/ansiHTML.ts @@ -147,8 +147,8 @@ export default function ansiHTML(text: string) { this.splice(0, count); } }); - let seq; let rep = ""; + let seq: string; while ((seq = match[0])) { match.advance(1); rep += applySeq(seq); diff --git a/packages/rspack-dev-server/src/middleware.ts b/packages/rspack-dev-server/src/middleware.ts index 79d0419e44f..8d3068639fc 100644 --- a/packages/rspack-dev-server/src/middleware.ts +++ b/packages/rspack-dev-server/src/middleware.ts @@ -63,7 +63,7 @@ export function getRspackMemoryAssets( if (!buffer) { return next(); } - let contentType; + let contentType: string; if (filename === "") { contentType = "text/html; charset=utf-8"; } else { diff --git a/packages/rspack-test-tools/src/compare/format-code.ts b/packages/rspack-test-tools/src/compare/format-code.ts index efbc4b2787a..f883755d0b7 100644 --- a/packages/rspack-test-tools/src/compare/format-code.ts +++ b/packages/rspack-test-tools/src/compare/format-code.ts @@ -79,8 +79,8 @@ export function formatCode( } }, IfStatement(path) { - let consequentNode; - let alternateNode; + let consequentNode: T.Statement | undefined; + let alternateNode: T.Statement | undefined; if (options.ignoreBlockOnlyStatement) { const consequent = path.get("consequent"); if ( diff --git a/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts b/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts index 2454e79e81c..bd08436025f 100644 --- a/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts +++ b/packages/rspack-test-tools/src/plugin/webpack-module-placeholder-plugin.ts @@ -12,7 +12,7 @@ function createRenderRuntimeModulesFn(Template) { const source = new ConcatSource(); for (const module of runtimeModules) { const codeGenerationResults = renderContext.codeGenerationResults; - let runtimeSource; + let runtimeSource: string; if (codeGenerationResults) { runtimeSource = codeGenerationResults.getSource( module, @@ -72,7 +72,7 @@ export class WebpackModulePlaceholderPlugin { hooks.renderModulePackage.tap( "RuntimeDiffPlugin", (moduleSource, module) => { - let cacheEntry; + let cacheEntry: unknown; let cache = caches.get(compilation); if (cache === undefined) { caches.set(compilation, (cache = new WeakMap())); diff --git a/packages/rspack-test-tools/src/processor/defaults.ts b/packages/rspack-test-tools/src/processor/defaults.ts index b988748303a..932c270092d 100644 --- a/packages/rspack-test-tools/src/processor/defaults.ts +++ b/packages/rspack-test-tools/src/processor/defaults.ts @@ -22,7 +22,7 @@ const escapedCwdRegExp = new RegExp( "g" ); const normalize = (str: string) => { - let normalizedStr; + let normalizedStr: string; if (CURRENT_CWD.startsWith("/")) { normalizedStr = str.replace( new RegExp(quoteMeta(CURRENT_CWD), "g"), @@ -70,7 +70,7 @@ export class DefaultsConfigProcessor< ) { super({ options: context => { - let res; + let res: TCompilerOptions; if (typeof _defaultsConfigOptions.options === "function") { res = _defaultsConfigOptions.options(context); } else { diff --git a/packages/rspack-test-tools/src/processor/hook.ts b/packages/rspack-test-tools/src/processor/hook.ts index f5e4b51daea..fb76aa71d15 100644 --- a/packages/rspack-test-tools/src/processor/hook.ts +++ b/packages/rspack-test-tools/src/processor/hook.ts @@ -108,7 +108,7 @@ export class HookCasesContext extends TestContext { context._addSnapshot(args, "input", group); const output = cb.apply(this, args); if (output && typeof output.then === "function") { - let resolve; + let resolve: ((value: void | PromiseLike) => void) | undefined; context.promises.push(new Promise(r => (resolve = r))); return output .then((o: unknown) => { diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index a73d8eec833..51ee7a0da6a 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -4626,14 +4626,14 @@ export const HtmlRspackPlugin: { publicPath?: string | undefined; hash?: boolean | undefined; chunks?: string[] | undefined; - template?: string | undefined; - templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; - templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; - inject?: boolean | "head" | "body" | undefined; base?: string | { target?: "_self" | "_blank" | "_parent" | "_top" | undefined; href?: string | undefined; } | undefined; + template?: string | undefined; + templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; + templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; + inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; @@ -4648,14 +4648,14 @@ export const HtmlRspackPlugin: { publicPath?: string | undefined; hash?: boolean | undefined; chunks?: string[] | undefined; - template?: string | undefined; - templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; - templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; - inject?: boolean | "head" | "body" | undefined; base?: string | { target?: "_self" | "_blank" | "_parent" | "_top" | undefined; href?: string | undefined; } | undefined; + template?: string | undefined; + templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; + templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; + inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; @@ -4729,14 +4729,14 @@ const htmlRspackPluginOptions: z.ZodObject<{ publicPath?: string | undefined; hash?: boolean | undefined; chunks?: string[] | undefined; - template?: string | undefined; - templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; - templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; - inject?: boolean | "head" | "body" | undefined; base?: string | { target?: "_self" | "_blank" | "_parent" | "_top" | undefined; href?: string | undefined; } | undefined; + template?: string | undefined; + templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; + templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; + inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; @@ -4749,14 +4749,14 @@ const htmlRspackPluginOptions: z.ZodObject<{ publicPath?: string | undefined; hash?: boolean | undefined; chunks?: string[] | undefined; - template?: string | undefined; - templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; - templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; - inject?: boolean | "head" | "body" | undefined; base?: string | { target?: "_self" | "_blank" | "_parent" | "_top" | undefined; href?: string | undefined; } | undefined; + template?: string | undefined; + templateContent?: string | ((args_0: Record, ...args_1: unknown[]) => string | Promise) | undefined; + templateParameters?: boolean | Record | ((args_0: Record, ...args_1: unknown[]) => Record | Promise>) | undefined; + inject?: boolean | "head" | "body" | undefined; scriptLoading?: "module" | "blocking" | "defer" | "systemjs-module" | undefined; excludeChunks?: string[] | undefined; sri?: "sha256" | "sha384" | "sha512" | undefined; diff --git a/packages/rspack/src/MultiCompiler.ts b/packages/rspack/src/MultiCompiler.ts index b8377b8368a..bbca1044531 100644 --- a/packages/rspack/src/MultiCompiler.ts +++ b/packages/rspack/src/MultiCompiler.ts @@ -66,7 +66,7 @@ export class MultiCompiler { compilers: Compiler[] | Record, options?: MultiCompilerOptions ) { - let normalizedCompilers; + let normalizedCompilers: Compiler[]; if (!Array.isArray(compilers)) { normalizedCompilers = Object.entries(compilers).map( ([name, compiler]) => { diff --git a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts index a0f6a79c243..65eeb114966 100644 --- a/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts +++ b/packages/rspack/src/builtin-plugin/HtmlRspackPlugin.ts @@ -204,7 +204,11 @@ const HtmlRspackPluginImpl = create( } const rawTemplateParameters = c.templateParameters; - let templateParameters; + let templateParameters: + | boolean + | Record + | ((params: string) => Promise) + | undefined; if (typeof rawTemplateParameters === "function") { templateParameters = async (data: string) => { const newData = await rawTemplateParameters(JSON.parse(data)); diff --git a/packages/rspack/src/builtin-plugin/css-extract/hmr/hotModuleReplacement.ts b/packages/rspack/src/builtin-plugin/css-extract/hmr/hotModuleReplacement.ts index 8ea16ed9d32..6f2e9593db4 100644 --- a/packages/rspack/src/builtin-plugin/css-extract/hmr/hotModuleReplacement.ts +++ b/packages/rspack/src/builtin-plugin/css-extract/hmr/hotModuleReplacement.ts @@ -74,7 +74,7 @@ function getCurrentScriptUrl(moduleId: string) { } function updateCss(el: HTMLLinkElement & Record, url?: string) { - let normalizedUrl; + let normalizedUrl: string; if (!url) { if (!el.href) { return; diff --git a/packages/rspack/src/builtin-plugin/css-extract/loader.ts b/packages/rspack/src/builtin-plugin/css-extract/loader.ts index db07f2d8ec4..e49c2eb9eb6 100644 --- a/packages/rspack/src/builtin-plugin/css-extract/loader.ts +++ b/packages/rspack/src/builtin-plugin/css-extract/loader.ts @@ -106,9 +106,7 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { const callback = this.async(); const filepath = this.resourcePath; - let { publicPath } = - /** @type {Compilation} */ - this._compilation!.outputOptions; + let { publicPath } = this._compilation!.outputOptions; if (typeof options.publicPath === "string") { // eslint-disable-next-line prefer-destructuring @@ -141,9 +139,8 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { | { default: Record; __esModule: true } | Record ) => { - /** @type {Locals | undefined} */ let locals: Record | undefined; - let namedExport; + let namedExport: boolean; const esModule = typeof options.esModule !== "undefined" ? options.esModule : true; @@ -167,9 +164,7 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { locals = {}; } - /** @type {Locals} */ locals[key] = ( - originalExports as Record - )[key]; + locals[key] = (originalExports as Record)[key]; } } } else { @@ -228,10 +223,7 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { const localsString = identifiers .map( - ([id, key]) => - `\nvar ${id} = ${stringifyLocal( - /** @type {Locals} */ locals![key] - )};` + ([id, key]) => `\nvar ${id} = ${stringifyLocal(locals![key])};` ) .join(""); const exportsString = `export { ${identifiers @@ -284,7 +276,7 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { `${this.resourcePath}.webpack[javascript/auto]!=!!!${request}`, { layer: options.layer, - publicPath: /** @type {Filename} */ publicPathForExtract, + publicPath: publicPathForExtract, baseUri: `${BASE_URI}/` }, (error, exports) => { diff --git a/packages/rspack/src/config/adapterRuleUse.ts b/packages/rspack/src/config/adapterRuleUse.ts index 27039e3d9c1..dc03f3c9eb8 100644 --- a/packages/rspack/src/config/adapterRuleUse.ts +++ b/packages/rspack/src/config/adapterRuleUse.ts @@ -253,16 +253,16 @@ function createRawModuleRuleUsesImpl( } return uses.map((use, index) => { - let o; + let o: string | undefined; let isBuiltin = false; if (use.loader.startsWith(BUILTIN_LOADER_PREFIX)) { - o = getBuiltinLoaderOptions(use.loader, use.options, options); + const temp = getBuiltinLoaderOptions(use.loader, use.options, options); // keep json with indent so miette can show pretty error - o = isNil(o) + o = isNil(temp) ? undefined - : typeof o === "string" - ? o - : JSON.stringify(o, null, 2); + : typeof temp === "string" + ? temp + : JSON.stringify(temp, null, 2); isBuiltin = true; } diff --git a/packages/rspack/src/lib/HookWebpackError.ts b/packages/rspack/src/lib/HookWebpackError.ts index 14667ad47d0..afdd6f4c731 100644 --- a/packages/rspack/src/lib/HookWebpackError.ts +++ b/packages/rspack/src/lib/HookWebpackError.ts @@ -73,7 +73,7 @@ export const makeWebpackErrorCallback = ( * @returns the result */ export const tryRunOrWebpackError = (fn: () => T, hook: string): T => { - let r; + let r: T; try { r = fn(); } catch (err) { diff --git a/packages/rspack/src/lib/cache/getLazyHashedEtag.ts b/packages/rspack/src/lib/cache/getLazyHashedEtag.ts index 567a2658c3b..3bad643325b 100644 --- a/packages/rspack/src/lib/cache/getLazyHashedEtag.ts +++ b/packages/rspack/src/lib/cache/getLazyHashedEtag.ts @@ -67,7 +67,7 @@ export const getter = ( obj: HashableObject, hashFunction: string | HashConstructor = "md4" ): LazyHashedEtag => { - let innerMap; + let innerMap: WeakMap | undefined; if (typeof hashFunction === "string") { innerMap = mapStrings.get(hashFunction); if (innerMap === undefined) { diff --git a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts index 10d6f014b74..84cb9d62974 100644 --- a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts +++ b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts @@ -125,7 +125,7 @@ const SIMPLE_PRINTERS: Record< nameMessage && versionMessage ? `${nameMessage} (${versionMessage})` : versionMessage || nameMessage || "Rspack"; - let statusMessage; + let statusMessage: string; if (errorsMessage && warningsMessage) { statusMessage = `compiled with ${errorsMessage} and ${warningsMessage}`; } else if (errorsMessage) { diff --git a/packages/rspack/src/stats/statsFactoryUtils.ts b/packages/rspack/src/stats/statsFactoryUtils.ts index e8e0e8afe7a..212f2198724 100644 --- a/packages/rspack/src/stats/statsFactoryUtils.ts +++ b/packages/rspack/src/stats/statsFactoryUtils.ts @@ -476,7 +476,7 @@ export const spaceLimited = ( if (limit < max) { // calculate how much we are over the size limit // this allows to approach the limit faster - let oversize; + let oversize: number; // If each group would take 1 line the total would be below the maximum // collapse some groups, keep items while ( diff --git a/packages/rspack/src/util/IterableHelpers.ts b/packages/rspack/src/util/IterableHelpers.ts index 48bf381419a..b44dbd5a736 100644 --- a/packages/rspack/src/util/IterableHelpers.ts +++ b/packages/rspack/src/util/IterableHelpers.ts @@ -9,7 +9,7 @@ */ export const last = (set: Iterable): T | undefined => { - let last; + let last: T | undefined; for (const item of set) last = item; return last; }; diff --git a/packages/rspack/src/util/bindingVersionCheck.ts b/packages/rspack/src/util/bindingVersionCheck.ts index f0a3b96a359..a1ae11f19d6 100644 --- a/packages/rspack/src/util/bindingVersionCheck.ts +++ b/packages/rspack/src/util/bindingVersionCheck.ts @@ -97,7 +97,7 @@ export const checkVersion = () => { return (result = platformArchAbi); } - let ADDON_VERSION; + let ADDON_VERSION: string; try { const BINDING_PKG_DIR = path.dirname( require.resolve("@rspack/binding/package.json") diff --git a/packages/rspack/src/util/cleverMerge.ts b/packages/rspack/src/util/cleverMerge.ts index 95e8eab13f1..bcb3b51188e 100644 --- a/packages/rspack/src/util/cleverMerge.ts +++ b/packages/rspack/src/util/cleverMerge.ts @@ -420,7 +420,7 @@ const mergeEntries = ( byValues: secondEntry.byValues }; } - let newBase; + let newBase: ObjectParsedPropertyEntry["base"]; const intermediateByValues = new Map(firstEntry.byValues); for (const [key, value] of intermediateByValues) { intermediateByValues.set( diff --git a/packages/rspack/src/util/hash/wasm-hash.ts b/packages/rspack/src/util/hash/wasm-hash.ts index 0c22a5b328c..04c7775fc92 100644 --- a/packages/rspack/src/util/hash/wasm-hash.ts +++ b/packages/rspack/src/util/hash/wasm-hash.ts @@ -86,7 +86,7 @@ class WasmHash { */ _updateWithShortString(data: string, encoding?: BufferEncoding): void { const { exports, buffered, mem, chunkSize } = this; - let endPos; + let endPos: number; if (data.length < 70) { if (!encoding || encoding === "utf-8" || encoding === "utf8") { endPos = buffered; diff --git a/website/components/builtIns/Table.tsx b/website/components/builtIns/Table.tsx index 12ad2893ba9..b306e1c19c4 100644 --- a/website/components/builtIns/Table.tsx +++ b/website/components/builtIns/Table.tsx @@ -72,15 +72,18 @@ export function Table(props: TableProps) { - {compiledValue.map((item: any, index: number) => ( - - {header.map(headerItem => ( - - {item[headerItem.key]} - - ))} - - ))} + {compiledValue.map((item: any, index: number) => { + const key = `row-${index}`; + return ( + + {header.map(headerItem => ( + + {item[headerItem.key]} + + ))} + + ); + })} ); From 092a71bfa49e6b1c91fca9232c08a864daa7c7c6 Mon Sep 17 00:00:00 2001 From: inottn Date: Tue, 10 Sep 2024 16:03:03 +0800 Subject: [PATCH 14/51] feat: make `namedChunkGroups` iterable (#7840) --- .../compilation-chunk-groups/rspack.config.js | 30 ++++++++++++++++ packages/rspack/src/Compilation.ts | 17 +++++----- packages/rspack/src/util/createReadonlyMap.ts | 34 +++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) create mode 100644 packages/rspack/src/util/createReadonlyMap.ts diff --git a/packages/rspack-test-tools/tests/configCases/plugins/compilation-chunk-groups/rspack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/compilation-chunk-groups/rspack.config.js index 49c3454b85b..3f2976413b9 100644 --- a/packages/rspack-test-tools/tests/configCases/plugins/compilation-chunk-groups/rspack.config.js +++ b/packages/rspack-test-tools/tests/configCases/plugins/compilation-chunk-groups/rspack.config.js @@ -12,6 +12,36 @@ function plugin(compiler) { expect(namedChunkGroups.get("a").getFiles()).toEqual(['a.js']); expect(namedChunkGroups.get("b").getFiles()).toEqual(['b.js']); + // for of + const result1 = []; + for (const [key, value] of namedChunkGroups) { + result1.push([key, value.getFiles()]); + } + result1.sort(([a], [b]) => (a > b ? 1 : -1)); + expect(result1).toEqual([ + ["a", ["a.js"]], + ["b", ["b.js"]] + ]); + + // forEach + const result2 = []; + namedChunkGroups.forEach((value, key) => { + result2.push([key, value.getFiles()]); + }); + result2.sort(([a], [b]) => (a > b ? 1 : -1)); + expect(result2).toEqual([ + ["a", ["a.js"]], + ["b", ["b.js"]] + ]); + + // values + const result3 = []; + for (const value of namedChunkGroups.values()) { + result3.push(value.getFiles()); + } + result3.sort(([a], [b]) => (a[0] > b[0] ? 1 : -1)); + expect(result3).toEqual([["a.js"], ["b.js"]]); + const origins = chunkGroups.reduce((res, i) => { res.push(...i.origins.map(i => i.module?.rawRequest).filter(Boolean)); return res; diff --git a/packages/rspack/src/Compilation.ts b/packages/rspack/src/Compilation.ts index 58b8f679e05..08a27291ad1 100644 --- a/packages/rspack/src/Compilation.ts +++ b/packages/rspack/src/Compilation.ts @@ -49,6 +49,7 @@ import { StatsFactory } from "./stats/StatsFactory"; import { StatsPrinter } from "./stats/StatsPrinter"; import { type AssetInfo, JsAssetInfo } from "./util/AssetInfo"; import MergeCaller from "./util/MergeCaller"; +import { createReadonlyMap } from "./util/createReadonlyMap"; import { createFakeCompilationDependencies } from "./util/fake"; import type { InputFileSystem } from "./util/fs"; import type Hash from "./util/hash"; @@ -403,10 +404,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si /** * Get the named chunk groups. * - * Note: This is a proxy for webpack internal API, only method `get` and `keys` are supported now. + * Note: This is a proxy for webpack internal API, only method `get`, `keys`, `values` and `entries` are supported now. */ - get namedChunkGroups(): ReadonlyMap> { - return { + get namedChunkGroups() { + return createReadonlyMap({ keys: (): IterableIterator => { const names = this.#inner.getNamedChunkGroupKeys(); return names[Symbol.iterator](); @@ -417,7 +418,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si return chunk && ChunkGroup.__from_binding(chunk, this.#inner); } } - } as Map>; + }); } get modules(): ReadonlySet { @@ -433,10 +434,10 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si /** * Get the named chunks. * - * Note: This is a proxy for webpack internal API, only method `get` and `keys` is supported now. + * Note: This is a proxy for webpack internal API, only method `get`, `keys`, `values` and `entries` are supported now. */ - get namedChunks(): ReadonlyMap> { - return { + get namedChunks() { + return createReadonlyMap({ keys: (): IterableIterator => { const names = this.#inner.getNamedChunkKeys(); return names[Symbol.iterator](); @@ -447,7 +448,7 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si return chunk && Chunk.__from_binding(chunk, this.#inner); } } - } as Map>; + }); } get entries(): Map { diff --git a/packages/rspack/src/util/createReadonlyMap.ts b/packages/rspack/src/util/createReadonlyMap.ts new file mode 100644 index 00000000000..d4ecd5019c9 --- /dev/null +++ b/packages/rspack/src/util/createReadonlyMap.ts @@ -0,0 +1,34 @@ +export function createReadonlyMap( + obj: Pick, "get" | "keys"> +) { + return { + ...obj, + *values() { + const keys = this.keys(); + for (const key of keys) { + yield this.get(key); + } + }, + *entries() { + const keys = this.keys(); + for (const key of keys) { + yield [key, this.get(key)]; + } + }, + forEach( + callback: ( + value: T, + key: string, + map: ReadonlyMap> + ) => void, + thisArg?: any + ): void { + for (const [key, value] of this) { + callback.call(thisArg, value, key, this); + } + }, + [Symbol.iterator]() { + return this.entries(); + } + } as ReadonlyMap>; +} From f152903e6d0f4a3bdd013327548d980038cf11a4 Mon Sep 17 00:00:00 2001 From: Hana Date: Tue, 10 Sep 2024 16:03:57 +0800 Subject: [PATCH 15/51] fix: loader intermediate data should be overridden between loaders (#7846) * fix: init * test: add --- .vscode/settings.json | 3 +- .../plugins/css_extract_additional_data.rs | 61 +++--- .../src/plugins/js_loader/context.rs | 10 +- .../src/plugins/js_loader/resolver.rs | 8 + .../src/plugins/js_loader/scheduler.rs | 20 +- crates/rspack_core/src/normal_module.rs | 7 +- .../rspack_core/src/parser_and_generator.rs | 2 +- crates/rspack_loader_lightningcss/src/lib.rs | 15 +- .../rspack_loader_preact_refresh/src/lib.rs | 6 +- crates/rspack_loader_react_refresh/src/lib.rs | 4 +- crates/rspack_loader_runner/src/context.rs | 198 +++++++++++++++++- crates/rspack_loader_runner/src/loader.rs | 37 +++- crates/rspack_loader_runner/src/runner.rs | 107 ++++++++-- crates/rspack_loader_swc/src/lib.rs | 7 +- crates/rspack_loader_testing/src/lib.rs | 45 +++- .../src/parser_plugin.rs | 5 +- .../src/visitors/dependency/mod.rs | 2 +- .../src/visitors/dependency/parser/mod.rs | 4 +- .../wth-css-extract-postcss/index.css | 4 + .../wth-css-extract-postcss/index.js | 24 +++ .../wth-css-extract-postcss/rspack.config.js | 46 ++++ .../additional-data-no-passthrough/a.js | 1 + .../additional-data-no-passthrough/index.js | 5 + .../loader-1.js | 5 + .../loader-2.js | 10 + .../rspack.config.js | 16 ++ .../loader/additional-data-passthrough/a.js | 1 + .../additional-data-passthrough/index.js | 7 + .../additional-data-passthrough/loader-1.js | 5 + .../additional-data-passthrough/loader-2.js | 10 + .../rspack.config.js | 16 ++ scripts/debug/launch.mjs | 2 +- 32 files changed, 596 insertions(+), 97 deletions(-) create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.css create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-1.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-2.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/a.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/index.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-1.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-2.js create mode 100644 packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/rspack.config.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b5e375a759..3410b4eeb0b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,5 +42,6 @@ "tokio", "ukey", "Ukey" - ] + ], + "rust-analyzer.checkOnSave": false } diff --git a/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs b/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs index 55a81a368c5..6063984837c 100644 --- a/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs +++ b/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs @@ -31,36 +31,43 @@ impl Debug for CssExtractRspackAdditionalDataPlugin { } #[plugin_hook(NormalModuleAdditionalData for CssExtractRspackAdditionalDataPlugin)] -async fn additional_data(&self, additional_data: &mut AdditionalData) -> Result<()> { - if !additional_data.contains::>() { +async fn additional_data(&self, additional_data: &mut Option<&mut AdditionalData>) -> Result<()> { + if !additional_data + .as_ref() + .is_some_and(|data| data.contains::>()) + { return Ok(()); } - let (tx, rx) = oneshot::channel::(); - let mut old_data = std::mem::take(additional_data); - self.js_callback.call(Box::new(move |env| { - if let Some(data) = old_data.get::>() - && let Ok(data) = data.get(env) - && let Ok(data) = data.coerce_to_object() - && let Ok(Some(data)) = data.get::<_, String>("css-extract-rspack-plugin") - { - let data_list: Vec = data - .split("__RSPACK_CSS_EXTRACT_SEP__") - .map(|info| { - serde_json::from_str(info) - .unwrap_or_else(|e| panic!("failed to parse CssExtractJsonData: {}", e)) - }) - .collect(); + if let Some(mut old_data) = additional_data.as_mut().map(|data| std::mem::take(*data)) { + let (tx, rx) = oneshot::channel::(); + self.js_callback.call(Box::new(move |env| { + if let Some(data) = old_data + .get::>() + .and_then(|data| data.get(env).ok()) + .and_then(|data| data.coerce_to_object().ok()) + .and_then(|data| data.get::<_, String>("css-extract-rspack-plugin").ok()) + .flatten() + { + let data_list: Vec = data + .split("__RSPACK_CSS_EXTRACT_SEP__") + .map(|info| { + serde_json::from_str(info) + .unwrap_or_else(|e| panic!("failed to parse CssExtractJsonData: {}", e)) + }) + .collect(); - old_data.insert(CssExtractJsonDataList(data_list)); - }; - tx.send(old_data) - .expect("should send `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); - })); - let new_data = rx - .await - .expect("should receive `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); - // ignore the default value here - let _ = std::mem::replace(additional_data, new_data); + old_data.insert(CssExtractJsonDataList(data_list)); + }; + tx.send(old_data) + .expect("should send `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); + })); + let new_data = rx + .await + .expect("should receive `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); + if let Some(data) = additional_data.as_mut() { + let _ = std::mem::replace(*data, new_data); + } + } Ok(()) } diff --git a/crates/rspack_binding_options/src/plugins/js_loader/context.rs b/crates/rspack_binding_options/src/plugins/js_loader/context.rs index 7f948a0c8f2..4315ca839a8 100644 --- a/crates/rspack_binding_options/src/plugins/js_loader/context.rs +++ b/crates/rspack_binding_options/src/plugins/js_loader/context.rs @@ -94,17 +94,17 @@ impl TryFrom<&mut LoaderContext> for JsLoaderContext { .to_js_module() .expect("CompilerModuleContext::to_js_module should not fail."), hot: cx.hot, - content: match &cx.content { + content: match cx.content() { Some(c) => Either::B(c.to_owned().into_bytes().into()), None => Either::A(Null), }, additional_data: cx - .additional_data - .get::>() + .additional_data() + .and_then(|data| data.get::>()) .cloned(), source_map: cx - .source_map - .clone() + .source_map() + .cloned() .map(|v| v.to_json()) .transpose() .map_err(|e| error!(e.to_string()))? diff --git a/crates/rspack_binding_options/src/plugins/js_loader/resolver.rs b/crates/rspack_binding_options/src/plugins/js_loader/resolver.rs index 7f476cbcb9d..e90670955a8 100644 --- a/crates/rspack_binding_options/src/plugins/js_loader/resolver.rs +++ b/crates/rspack_binding_options/src/plugins/js_loader/resolver.rs @@ -75,6 +75,8 @@ pub fn get_builtin_loader(builtin: &str, options: Option<&str>) -> Result) -> Result, mut from: JsLoaderContext, ) -> Result<()> { - if let Some(data) = &from.additional_data { - to.additional_data.insert(data.clone()); - } to.cacheable = from.cacheable; to.file_dependencies = from.file_dependencies.into_iter().map(Into::into).collect(); to.context_dependencies = from @@ -66,16 +63,23 @@ pub(crate) fn merge_loader_context( .into_iter() .map(Into::into) .collect(); - to.content = match from.content { + + let content = match from.content { Either::A(_) => None, Either::B(c) => Some(rspack_core::Content::from(Into::>::into(c))), }; - to.source_map = from + let source_map = from .source_map .as_ref() .map(|s| rspack_core::rspack_sources::SourceMap::from_slice(s)) .transpose() .map_err(|e| error!(e.to_string()))?; + let additional_data = from.additional_data.take().map(|data| { + let mut additional = AdditionalData::default(); + additional.insert(data); + additional + }); + to.__finish_with((content, source_map, additional_data)); // update loader status to.loader_items = to @@ -90,6 +94,8 @@ pub(crate) fn merge_loader_context( to.set_pitch_executed() } to.set_data(from.data); + // JS loader should always be considered as finished + to.set_finish_called(); to }) .collect(); diff --git a/crates/rspack_core/src/normal_module.rs b/crates/rspack_core/src/normal_module.rs index ad82448b83d..704bfdc20cd 100644 --- a/crates/rspack_core/src/normal_module.rs +++ b/crates/rspack_core/src/normal_module.rs @@ -76,7 +76,7 @@ define_hook!(NormalModuleLoader: SyncSeries(loader_context: &mut LoaderContext) -> bool); define_hook!(NormalModuleLoaderStartYielding: AsyncSeries(loader_context: &mut LoaderContext)); define_hook!(NormalModuleBeforeLoaders: SyncSeries(module: &mut NormalModule)); -define_hook!(NormalModuleAdditionalData: AsyncSeries(additional_data: &mut AdditionalData)); +define_hook!(NormalModuleAdditionalData: AsyncSeries(additional_data: &mut Option<&mut AdditionalData>)); #[derive(Debug, Default)] pub struct NormalModuleHooks { @@ -410,14 +410,11 @@ impl Module for NormalModule { current_loader: Default::default(), }); - let additional_data = AdditionalData::default(); - let loader_result = run_loaders( self.loaders.clone(), self.resource_data.clone(), Some(plugin.clone()), build_context.runner_context, - additional_data, ) .await; let (mut loader_result, ds) = match loader_result { @@ -448,7 +445,7 @@ impl Module for NormalModule { .plugin_driver .normal_module_hooks .additional_data - .call(&mut loader_result.additional_data) + .call(&mut loader_result.additional_data.as_mut()) .await?; self.add_diagnostics(ds); diff --git a/crates/rspack_core/src/parser_and_generator.rs b/crates/rspack_core/src/parser_and_generator.rs index 271999144ec..809d4bb97bf 100644 --- a/crates/rspack_core/src/parser_and_generator.rs +++ b/crates/rspack_core/src/parser_and_generator.rs @@ -32,7 +32,7 @@ pub struct ParseContext<'a> { pub loaders: &'a [BoxLoader], pub resource_data: &'a ResourceData, pub compiler_options: &'a CompilerOptions, - pub additional_data: AdditionalData, + pub additional_data: Option, pub build_info: &'a mut BuildInfo, pub build_meta: &'a mut BuildMeta, } diff --git a/crates/rspack_loader_lightningcss/src/lib.rs b/crates/rspack_loader_lightningcss/src/lib.rs index 9aec6700a3c..49960a725b2 100644 --- a/crates/rspack_loader_lightningcss/src/lib.rs +++ b/crates/rspack_loader_lightningcss/src/lib.rs @@ -49,7 +49,7 @@ impl LightningCssLoader { let filename = resource_path.as_str().to_string(); - let Some(content) = std::mem::take(&mut loader_context.content) else { + let Some(content) = loader_context.take_content() else { return Ok(()); }; @@ -131,8 +131,7 @@ impl LightningCssLoader { let enable_sourcemap = loader_context.context.module_source_map_kind.enabled(); let mut source_map = loader_context - .source_map - .as_ref() + .source_map() .map(|input_source_map| -> Result<_> { let mut sm = parcel_sourcemap::SourceMap::new( input_source_map @@ -180,15 +179,17 @@ impl LightningCssLoader { }) .map_err(|_| rspack_error::error!("failed to generate css"))?; - loader_context.content = Some(rspack_core::Content::String(content.code)); - if enable_sourcemap { let source_map = source_map .to_json(None) .map_err(|e| rspack_error::error!(e.to_string()))?; - loader_context.source_map = - Some(SourceMap::from_json(&source_map).expect("should be able to generate source-map")); + loader_context.finish_with(( + content.code, + SourceMap::from_json(&source_map).expect("should be able to generate source-map"), + )); + } else { + loader_context.finish_with(content.code); } Ok(()) diff --git a/crates/rspack_loader_preact_refresh/src/lib.rs b/crates/rspack_loader_preact_refresh/src/lib.rs index eb77a20f8ff..8c06dba9e33 100644 --- a/crates/rspack_loader_preact_refresh/src/lib.rs +++ b/crates/rspack_loader_preact_refresh/src/lib.rs @@ -27,11 +27,13 @@ impl PreactRefreshLoader { #[async_trait::async_trait] impl Loader for PreactRefreshLoader { async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { - let content = std::mem::take(&mut loader_context.content).expect("Content should be available"); + let content = loader_context + .take_content() + .expect("Content should be available"); let mut source = content.try_into_string()?; source += "\n"; source += include_str!("runtime.js"); - loader_context.content = Some(source.into()); + loader_context.finish_with(source); Ok(()) } } diff --git a/crates/rspack_loader_react_refresh/src/lib.rs b/crates/rspack_loader_react_refresh/src/lib.rs index ba85db67918..8f92ec12bd0 100644 --- a/crates/rspack_loader_react_refresh/src/lib.rs +++ b/crates/rspack_loader_react_refresh/src/lib.rs @@ -27,7 +27,7 @@ impl ReactRefreshLoader { #[async_trait::async_trait] impl Loader for ReactRefreshLoader { async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { - let Some(content) = std::mem::take(&mut loader_context.content) else { + let Some(content) = loader_context.take_content() else { return Ok(()); }; let mut source = content.try_into_string()?; @@ -42,7 +42,7 @@ Promise.resolve().then(function() { $ReactRefreshRuntime$.refresh(__webpack_module__.id, __webpack_module__.hot); }); "#; - loader_context.content = Some(source.into()); + loader_context.finish_with(source); Ok(()) } } diff --git a/crates/rspack_loader_runner/src/context.rs b/crates/rspack_loader_runner/src/context.rs index 162c2539299..573693c250a 100644 --- a/crates/rspack_loader_runner/src/context.rs +++ b/crates/rspack_loader_runner/src/context.rs @@ -37,14 +37,14 @@ impl State { pub struct LoaderContext { pub hot: bool, pub resource_data: Arc, - - pub content: Option, #[derivative(Debug = "ignore")] pub context: Context, - pub source_map: Option, - pub additional_data: AdditionalData, - pub cacheable: bool, + pub(crate) content: Option, + pub(crate) source_map: Option, + pub(crate) additional_data: Option, + + pub cacheable: bool, pub file_dependencies: HashSet, pub context_dependencies: HashSet, pub missing_dependencies: HashSet, @@ -118,8 +118,196 @@ impl LoaderContext { self.resource_data.resource_fragment.as_deref() } + pub fn content(&self) -> Option<&Content> { + self.content.as_ref() + } + + pub fn source_map(&self) -> Option<&SourceMap> { + self.source_map.as_ref() + } + + pub fn additional_data(&self) -> Option<&AdditionalData> { + self.additional_data.as_ref() + } + + pub fn take_content(&mut self) -> Option { + self.content.take() + } + + pub fn take_source_map(&mut self) -> Option { + self.source_map.take() + } + + pub fn take_additional_data(&mut self) -> Option { + self.additional_data.take() + } + + pub fn take_all(&mut self) -> (Option, Option, Option) { + ( + self.content.take(), + self.source_map.take(), + self.additional_data.take(), + ) + } + + pub fn finish_with(&mut self, patch: impl Into) { + self.__finish_with(patch); + self.current_loader().set_finish_called(); + } + + pub fn finish_with_empty(&mut self) { + self.content = None; + self.source_map = None; + self.additional_data = None; + self.current_loader().set_finish_called(); + } + #[inline] pub fn state(&self) -> State { self.state } + + #[doc(hidden)] + pub fn __finish_with(&mut self, patch: impl Into) { + let patch = patch.into(); + self.content = patch.content; + self.source_map = patch.source_map; + self.additional_data = patch.additional_data; + } +} + +pub struct LoaderPatch { + pub(crate) content: Option, + pub(crate) source_map: Option, + pub(crate) additional_data: Option, +} + +impl From for LoaderPatch +where + T: Into, +{ + fn from(content: T) -> Self { + Self { + content: Some(content.into()), + source_map: None, + additional_data: None, + } + } +} + +impl From<(T, SourceMap)> for LoaderPatch +where + T: Into, +{ + fn from(value: (T, SourceMap)) -> Self { + Self { + content: Some(value.0.into()), + source_map: Some(value.1), + additional_data: None, + } + } +} + +impl From<(T, Option)> for LoaderPatch +where + T: Into, +{ + fn from(value: (T, Option)) -> Self { + Self { + content: Some(value.0.into()), + source_map: value.1, + additional_data: None, + } + } +} + +impl From<(T, SourceMap, AdditionalData)> for LoaderPatch +where + T: Into, +{ + fn from(value: (T, SourceMap, AdditionalData)) -> Self { + Self { + content: Some(value.0.into()), + source_map: Some(value.1), + additional_data: Some(value.2), + } + } +} + +impl From<(T, Option, Option)> for LoaderPatch +where + T: Into, +{ + fn from(value: (T, Option, Option)) -> Self { + Self { + content: Some(value.0.into()), + source_map: value.1, + additional_data: value.2, + } + } +} + +impl From> for LoaderPatch +where + T: Into, +{ + fn from(content: Option) -> Self { + Self { + content: content.map(|c| c.into()), + source_map: None, + additional_data: None, + } + } +} + +impl From<(Option, SourceMap)> for LoaderPatch +where + T: Into, +{ + fn from(value: (Option, SourceMap)) -> Self { + Self { + content: value.0.map(|c| c.into()), + source_map: Some(value.1), + additional_data: None, + } + } +} + +impl From<(Option, Option)> for LoaderPatch +where + T: Into, +{ + fn from(value: (Option, Option)) -> Self { + Self { + content: value.0.map(|c| c.into()), + source_map: value.1, + additional_data: None, + } + } +} + +impl From<(Option, SourceMap, AdditionalData)> for LoaderPatch +where + T: Into, +{ + fn from(value: (Option, SourceMap, AdditionalData)) -> Self { + Self { + content: value.0.map(|c| c.into()), + source_map: Some(value.1), + additional_data: Some(value.2), + } + } +} + +impl From<(Option, Option, Option)> for LoaderPatch +where + T: Into, +{ + fn from(value: (Option, Option, Option)) -> Self { + Self { + content: value.0.map(|c| c.into()), + source_map: value.1, + additional_data: value.2, + } + } } diff --git a/crates/rspack_loader_runner/src/loader.rs b/crates/rspack_loader_runner/src/loader.rs index 749d7de29c2..787a3d1b2eb 100644 --- a/crates/rspack_loader_runner/src/loader.rs +++ b/crates/rspack_loader_runner/src/loader.rs @@ -39,6 +39,14 @@ pub struct LoaderItem { r#type: String, pitch_executed: AtomicBool, normal_executed: AtomicBool, + /// Whether loader was called with [LoaderContext::finish_with]. + /// + /// Indicates that the loader has finished its work, + /// otherwise loader runner will reset [`LoaderContext::content`], [`LoaderContext::source_map`], [`LoaderContext::additional_data`]. + /// + /// This flag is used to align with webpack's behavior: + /// If nothing is modified in the loader, the loader will reset the content, source map, and additional data. + finish_called: AtomicBool, } impl LoaderItem { @@ -62,11 +70,13 @@ impl LoaderItem { } #[inline] + #[doc(hidden)] pub fn set_data(&mut self, data: serde_json::Value) { self.data = data; } #[inline] + #[doc(hidden)] pub fn pitch_executed(&self) -> bool { self.pitch_executed.load(Ordering::Relaxed) } @@ -77,14 +87,28 @@ impl LoaderItem { } #[inline] + #[doc(hidden)] + pub fn finish_called(&self) -> bool { + self.finish_called.load(Ordering::Relaxed) + } + + #[inline] + #[doc(hidden)] pub fn set_pitch_executed(&self) { self.pitch_executed.store(true, Ordering::Relaxed) } #[inline] + #[doc(hidden)] pub fn set_normal_executed(&self) { self.normal_executed.store(true, Ordering::Relaxed) } + + #[inline] + #[doc(hidden)] + pub fn set_finish_called(&self) { + self.finish_called.store(true, Ordering::Relaxed) + } } impl Display for LoaderItem { @@ -141,9 +165,14 @@ impl Identifiable for LoaderItem { } #[async_trait] -pub trait Loader: Identifiable + Send + Sync { - async fn run(&self, _loader_context: &mut LoaderContext) -> Result<()> { - // noop +pub trait Loader: Identifiable + Send + Sync +where + Context: Send, +{ + async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { + // If loader does not implement normal stage, + // it should inherit the result from the previous loader. + loader_context.current_loader().set_finish_called(); Ok(()) } async fn pitch(&self, _loader_context: &mut LoaderContext) -> Result<()> { @@ -177,6 +206,7 @@ impl From>> for LoaderItem { r#type: r#type.to_string(), pitch_executed: AtomicBool::new(false), normal_executed: AtomicBool::new(false), + finish_called: AtomicBool::new(false), }; } let ident = loader.identifier(); @@ -195,6 +225,7 @@ impl From>> for LoaderItem { r#type: String::default(), pitch_executed: AtomicBool::new(false), normal_executed: AtomicBool::new(false), + finish_called: AtomicBool::new(false), } } } diff --git a/crates/rspack_loader_runner/src/runner.rs b/crates/rspack_loader_runner/src/runner.rs index d096b30538d..83e55a53894 100644 --- a/crates/rspack_loader_runner/src/runner.rs +++ b/crates/rspack_loader_runner/src/runner.rs @@ -62,7 +62,6 @@ async fn create_loader_context( resource_data: Arc, plugin: Option>>, context: Context, - additional_data: AdditionalData, ) -> Result> { let mut file_dependencies: HashSet = Default::default(); if let Some(resource_path) = &resource_data.resource_path @@ -81,7 +80,7 @@ async fn create_loader_context( content: None, context, source_map: None, - additional_data, + additional_data: None, state: State::Init, loader_index: 0, loader_items, @@ -102,15 +101,13 @@ pub async fn run_loaders( resource_data: Arc, plugins: Option>>, context: Context, - additional_data: AdditionalData, ) -> Result> { let loaders = loaders .into_iter() .map(|i| i.into()) .collect::>>(); - let mut cx = - create_loader_context(loaders, resource_data, plugins, context, additional_data).await?; + let mut cx = create_loader_context(loaders, resource_data, plugins, context).await?; loop { match cx.state { @@ -173,6 +170,12 @@ pub async fn run_loaders( cx.current_loader().set_normal_executed(); let loader = cx.current_loader().loader().clone(); loader.run(&mut cx).await?; + if !cx.current_loader().finish_called() { + // If nothing is returned from this loader, + // we set everything to [None] and move to the next loader. + // This mocks the behavior of webpack loader-runner. + cx.finish_with_empty(); + } } State::Finished => break, } @@ -190,7 +193,7 @@ pub struct LoaderResult { pub build_dependencies: HashSet, pub content: Content, pub source_map: Option, - pub additional_data: AdditionalData, + pub additional_data: Option, } impl TryFrom> for TWithDiagnosticArray { @@ -230,7 +233,7 @@ mod test { use rspack_error::Result; use super::{run_loaders, Loader, LoaderContext, ResourceData}; - use crate::{content::Content, plugin::LoaderRunnerPlugin}; + use crate::{content::Content, plugin::LoaderRunnerPlugin, AdditionalData}; struct TestContentPlugin; @@ -404,15 +407,16 @@ mod test { encoded_content: None, }); - run_loaders( + // Ignore error: Final loader didn't return a Buffer or String + assert!(run_loaders( vec![p1, p2, c1, c2], rs.clone(), Some(Arc::new(TestContentPlugin)), (), - Default::default(), ) .await - .unwrap(); + .err() + .is_some()); IDENTS.with(|i| assert_eq!(*i.borrow(), &["pitch1", "pitch2", "normal2", "normal1"])); IDENTS.with(|i| i.borrow_mut().clear()); @@ -420,15 +424,16 @@ mod test { let p2 = Arc::new(PitchNormal) as Arc>; let p3 = Arc::new(PitchNormal2) as Arc>; - run_loaders( + // Ignore error: Final loader didn't return a Buffer or String + assert!(run_loaders( vec![p1, p2, p3], rs.clone(), Some(Arc::new(TestContentPlugin)), (), - Default::default(), ) .await - .unwrap(); + .err() + .is_some()); IDENTS.with(|i| { // should not execute p3, as p2 pitched successfully. assert!(!i.borrow().contains(&"pitch-normal-normal-2".to_string())); @@ -456,8 +461,14 @@ mod test { #[async_trait::async_trait] impl Loader<()> for Normal { async fn run(&self, loader_context: &mut LoaderContext<()>) -> Result<()> { - let data = loader_context.additional_data.get::<&str>().unwrap(); + let data = loader_context + .additional_data + .as_ref() + .unwrap() + .get::<&str>() + .unwrap(); assert_eq!(*data, "additional-data"); + loader_context.finish_with(("".to_string(), None, None)); Ok(()) } } @@ -473,7 +484,9 @@ mod test { #[async_trait::async_trait] impl Loader<()> for Normal2 { async fn run(&self, loader_context: &mut LoaderContext<()>) -> Result<()> { - loader_context.additional_data.insert("additional-data"); + let mut additional_data: AdditionalData = Default::default(); + additional_data.insert("additional-data"); + loader_context.finish_with(("".to_string(), None, Some(additional_data))); Ok(()) } } @@ -496,9 +509,71 @@ mod test { rs, Some(Arc::new(TestContentPlugin)), (), - Default::default(), ) .await .unwrap(); } + + #[tokio::test] + async fn should_override_data_if_finish_with_is_not_called() { + struct Normal; + + impl Identifiable for Normal { + fn identifier(&self) -> Identifier { + "/rspack/normal-loader1".into() + } + } + + #[async_trait::async_trait] + impl Loader<()> for Normal { + async fn run(&self, loader_context: &mut LoaderContext<()>) -> Result<()> { + assert!(loader_context.content.is_some()); + // Does not call `LoaderContext::finish_with` + Ok(()) + } + } + + let rs = Arc::new(ResourceData { + scheme: OnceCell::new(), + resource: "/rspack/main.js?abc=123#efg".to_owned(), + resource_description: None, + resource_fragment: None, + resource_query: None, + resource_path: Default::default(), + mimetype: None, + parameters: None, + encoding: None, + encoded_content: None, + }); + + struct Normal2; + + impl Identifiable for Normal2 { + fn identifier(&self) -> Identifier { + "/rspack/normal-loader2".into() + } + } + + #[async_trait::async_trait] + impl Loader<()> for Normal2 { + async fn run(&self, loader_context: &mut LoaderContext<()>) -> Result<()> { + let (content, source_map, additional_data) = loader_context.take_all(); + assert!(content.is_none()); + assert!(source_map.is_none()); + assert!(additional_data.is_none()); + Ok(()) + } + } + + // Ignore error: Final loader didn't return a Buffer or String + assert!(run_loaders( + vec![Arc::new(Normal2), Arc::new(Normal)], + rs, + Some(Arc::new(TestContentPlugin)), + (), + ) + .await + .err() + .is_some()); + } } diff --git a/crates/rspack_loader_swc/src/lib.rs b/crates/rspack_loader_swc/src/lib.rs index bb644f0d538..4f471c106eb 100644 --- a/crates/rspack_loader_swc/src/lib.rs +++ b/crates/rspack_loader_swc/src/lib.rs @@ -48,7 +48,7 @@ impl SwcLoader { .resource_path() .map(|p| p.to_path_buf()) .unwrap_or_default(); - let Some(content) = std::mem::take(&mut loader_context.content) else { + let Some(content) = loader_context.take_content() else { return Ok(()); }; @@ -64,7 +64,7 @@ impl SwcLoader { .transform .merge(MergingOption::from(Some(transform))); } - if let Some(pre_source_map) = loader_context.source_map.clone() { + if let Some(pre_source_map) = loader_context.source_map().cloned() { if let Ok(source_map) = pre_source_map.to_json() { swc_options.config.input_source_map = Some(InputSourceMap::Str(source_map)) } @@ -133,12 +133,11 @@ impl SwcLoader { let ast = c.into_js_ast(program); let TransformOutput { code, map } = ast::stringify(&ast, codegen_options)?; - loader_context.content = Some(code.into()); let map = map .map(|m| SourceMap::from_json(&m)) .transpose() .map_err(|e| error!(e.to_string()))?; - loader_context.source_map = map; + loader_context.finish_with((code, map)); Ok(()) } diff --git a/crates/rspack_loader_testing/src/lib.rs b/crates/rspack_loader_testing/src/lib.rs index f4cd8101dfd..3c0c55d0e02 100644 --- a/crates/rspack_loader_testing/src/lib.rs +++ b/crates/rspack_loader_testing/src/lib.rs @@ -10,11 +10,11 @@ pub struct SimpleLoader; #[async_trait] impl Loader for SimpleLoader { async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { - let Some(content) = loader_context.content.take() else { + let Some(content) = loader_context.take_content() else { return Ok(()); }; let export = format!("{}-simple", content.try_into_string()?); - loader_context.content = Some(format!("module.exports = {}", json!(export)).into()); + loader_context.finish_with(format!("module.exports = {}", json!(export))); Ok(()) } } @@ -29,10 +29,10 @@ pub struct SimpleAsyncLoader; #[async_trait] impl Loader for SimpleAsyncLoader { async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { - let Some(content) = loader_context.content.take() else { + let Some(content) = loader_context.take_content() else { return Ok(()); }; - loader_context.content = Some(format!("{}-async-simple", content.try_into_string()?).into()); + loader_context.finish_with(format!("{}-async-simple", content.try_into_string()?)); Ok(()) } } @@ -47,15 +47,14 @@ pub struct PitchingLoader; #[async_trait] impl Loader for PitchingLoader { async fn pitch(&self, loader_context: &mut LoaderContext) -> Result<()> { - loader_context.content = Some( + loader_context.finish_with( [ loader_context .remaining_request() .display_with_suffix(loader_context.resource()), loader_context.previous_request().to_string(), ] - .join(":") - .into(), + .join(":"), ); Ok(()) } @@ -66,3 +65,35 @@ impl Identifiable for PitchingLoader { } } pub const PITCHING_LOADER_IDENTIFIER: &str = "builtin:test-pitching-loader"; + +pub struct PassthroughLoader; +#[async_trait] +impl Loader for PassthroughLoader { + async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { + let patch_data = loader_context.take_all(); + loader_context.finish_with(patch_data); + Ok(()) + } +} +impl Identifiable for PassthroughLoader { + fn identifier(&self) -> Identifier { + PASS_THROUGH_LOADER_IDENTIFIER.into() + } +} +pub const PASS_THROUGH_LOADER_IDENTIFIER: &str = "builtin:test-passthrough-loader"; + +pub struct NoPassthroughLoader; +#[async_trait] +impl Loader for NoPassthroughLoader { + async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { + let (content, _, _) = loader_context.take_all(); + loader_context.finish_with(content); + Ok(()) + } +} +impl Identifiable for NoPassthroughLoader { + fn identifier(&self) -> Identifier { + NO_PASS_THROUGH_LOADER_IDENTIFIER.into() + } +} +pub const NO_PASS_THROUGH_LOADER_IDENTIFIER: &str = "builtin:test-no-passthrough-loader"; diff --git a/crates/rspack_plugin_extract_css/src/parser_plugin.rs b/crates/rspack_plugin_extract_css/src/parser_plugin.rs index 488ed607927..6a0b0579d40 100644 --- a/crates/rspack_plugin_extract_css/src/parser_plugin.rs +++ b/crates/rspack_plugin_extract_css/src/parser_plugin.rs @@ -28,7 +28,10 @@ pub struct PluginCssExtractParserPlugin { impl JavascriptParserPlugin for PluginCssExtractParserPlugin { fn finish(&self, parser: &mut JavascriptParser) -> Option { - let deps = if let Some(additional_data) = parser.additional_data.get::() + let deps = if let Some(additional_data) = parser + .additional_data + .as_ref() + .and_then(|data| data.get::()) { if let Some(deps) = self.cache.get(additional_data) { deps.clone() diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs index d4842c278ab..090270c2926 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs @@ -56,7 +56,7 @@ pub fn scan_dependencies( semicolons: &mut FxHashSet, unresolved_mark: Mark, parser_plugins: &mut Vec, - additional_data: AdditionalData, + additional_data: Option, ) -> Result>> { let mut parser = JavascriptParser::new( source_map, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs index 4d16881023c..1e6290f7e62 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -212,7 +212,7 @@ pub struct JavascriptParser<'parser> { #[allow(clippy::vec_box)] pub(crate) blocks: Vec>, // TODO: remove `additional_data` once we have builtin:css-extract-loader - pub additional_data: AdditionalData, + pub additional_data: Option, pub(crate) comments: Option<&'parser dyn Comments>, pub(crate) worker_index: u32, pub(crate) build_meta: &'parser mut BuildMeta, @@ -265,7 +265,7 @@ impl<'parser> JavascriptParser<'parser> { semicolons: &'parser mut FxHashSet, unresolved_mark: Mark, parser_plugins: &'parser mut Vec, - additional_data: AdditionalData, + additional_data: Option, ) -> Self { let warning_diagnostics: Vec> = Vec::with_capacity(4); let errors = Vec::with_capacity(4); diff --git a/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.css b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.css new file mode 100644 index 00000000000..cd1bf79c24f --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.css @@ -0,0 +1,4 @@ +.foo { + user-select: none; + font-size: 32px; +} diff --git a/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.js b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.js new file mode 100644 index 00000000000..e4dcfca4d13 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/index.js @@ -0,0 +1,24 @@ +import './index.css' + +const fs = __non_webpack_require__("node:fs"); +const path = __non_webpack_require__("node:path"); + +it("should transform CSS and add prefixes correctly", () => { + const css = fs.readFileSync( + path.resolve(__dirname, "./bundle0.css"), + "utf-8" + ); + + expect(css.includes('-ms-user-select: none;')).toBeTruthy(); + expect(css.includes('user-select: none;')).toBeTruthy(); +}); + +it("should perform px to rem transformation", () => { + const css = fs.readFileSync( + path.resolve(__dirname, "./bundle0.css"), + "utf-8" + ); + + expect(css.includes('px')).toBeFalsy(); + expect(css.includes('rem')).toBeTruthy(); +}); diff --git a/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/rspack.config.js b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/rspack.config.js new file mode 100644 index 00000000000..12c98b1c362 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-lightningcss-loader/wth-css-extract-postcss/rspack.config.js @@ -0,0 +1,46 @@ +const rspack = require("@rspack/core"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + target: 'web', + node: false, + module: { + rules: [ + { + test: /\.css$/, + use: [ + rspack.CssExtractRspackPlugin.loader, + "css-loader", + { + loader: "builtin:lightningcss-loader", + /** @type {import("@rspack/core").LightningcssLoaderOptions} */ + options: { + targets: [ + 'Edge >= 12' + ] + } + }, + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: [ + "postcss-pxtorem" + ] + } + } + } + ], + type: "javascript/auto" + } + ] + }, + plugins: [ + new rspack.CssExtractRspackPlugin({ + filename: 'bundle0.css' + }) + ], + experiments: { + css: true + } +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/a.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/a.js new file mode 100644 index 00000000000..6cd1d0075d4 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/a.js @@ -0,0 +1 @@ +module.exports = "a"; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/index.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/index.js new file mode 100644 index 00000000000..75cdb63c65d --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/index.js @@ -0,0 +1,5 @@ +it("should not passthrough additional data if builtin loader didn't reuse additional data", () => { + let result = require("./a"); + expect(Object.keys(result)).not.toContain("a"); + expect(Object.keys(result)).toContain("b"); +}); diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-1.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-1.js new file mode 100644 index 00000000000..9d88459441c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-1.js @@ -0,0 +1,5 @@ +module.exports = function (content, sourceMap, additionalData) { + this.callback(null, content, null, { + a: "a" + }); +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-2.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-2.js new file mode 100644 index 00000000000..50d47afe5eb --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/loader-2.js @@ -0,0 +1,10 @@ +module.exports = function (content, sourceMap, additionalData) { + this.callback( + null, + `module.exports = ${JSON.stringify({ + ...additionalData, + b: "b" + })}`, + null + ); +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/rspack.config.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/rspack.config.js new file mode 100644 index 00000000000..b3a4b38a2bc --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-no-passthrough/rspack.config.js @@ -0,0 +1,16 @@ +const path = require("path"); + +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + context: __dirname, + module: { + rules: [ + { + test: path.join(__dirname, "a.js"), + use: [{ loader: "./loader-2.js" }, { loader: "builtin:test-no-passthrough-loader" }, { loader: "./loader-1.js" }] + } + ] + } +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/a.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/a.js new file mode 100644 index 00000000000..6cd1d0075d4 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/a.js @@ -0,0 +1 @@ +module.exports = "a"; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/index.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/index.js new file mode 100644 index 00000000000..d2b47aacafe --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/index.js @@ -0,0 +1,7 @@ +it("should pass additional data between loaders if builtin loader passes through its additional data", () => { + let result = require("./a"); + expect(result).toEqual({ + a: "a", + b: "b" + }); +}); diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-1.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-1.js new file mode 100644 index 00000000000..9d88459441c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-1.js @@ -0,0 +1,5 @@ +module.exports = function (content, sourceMap, additionalData) { + this.callback(null, content, null, { + a: "a" + }); +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-2.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-2.js new file mode 100644 index 00000000000..50d47afe5eb --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/loader-2.js @@ -0,0 +1,10 @@ +module.exports = function (content, sourceMap, additionalData) { + this.callback( + null, + `module.exports = ${JSON.stringify({ + ...additionalData, + b: "b" + })}`, + null + ); +}; diff --git a/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/rspack.config.js b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/rspack.config.js new file mode 100644 index 00000000000..9e66fb89655 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/loader/additional-data-passthrough/rspack.config.js @@ -0,0 +1,16 @@ +const path = require("path"); + +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + context: __dirname, + module: { + rules: [ + { + test: path.join(__dirname, "a.js"), + use: [{ loader: "./loader-2.js" }, { loader: "builtin:test-passthrough-loader" }, { loader: "./loader-1.js" }] + } + ] + } +}; diff --git a/scripts/debug/launch.mjs b/scripts/debug/launch.mjs index fb0dd7eb63b..7d5c8cc4a6c 100644 --- a/scripts/debug/launch.mjs +++ b/scripts/debug/launch.mjs @@ -33,7 +33,7 @@ export async function launchJestWithArgs(additionalArgs) { "--expose-gc", "--max-old-space-size=8192", "--experimental-vm-modules", - "${workspaceFolder}/node_modules/.bin/jest", + "${workspaceFolder}/node_modules/jest-cli/bin/jest", "--runInBand", "--logHeapUsage" ]; From 28f3e7a4fa5d645e3c89d323276249072a839ef9 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Tue, 10 Sep 2024 18:00:29 +0800 Subject: [PATCH 16/51] ci: disable dev server test cases (#7851) --- .github/workflows/reusable-build.yml | 6 +++++ packages/rspack-dev-server/jest.config.js | 27 ++++++++++++----------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index fc86ba1b572..f4fb03977a8 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -298,6 +298,12 @@ jobs: with: node-version: ${{ matrix.node }} + - name: Setup firefox + if: ${{ !contains(inputs.target, 'windows') && !inputs.skipable }} + uses: browser-actions/setup-firefox@latest + with: + firefox-version: latest + ### x86_64-unknown-linux-gnu - name: Test x86_64-unknown-linux-gnu diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index acb4b7b6a8b..9999c192490 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -8,20 +8,21 @@ const config = { }, testMatch: [ "/tests/*.test.ts", - ...(isWin ? [] : ["/tests/e2e/*.test.js"]) + ...(isWin || process.env.CI ? [] : ["/tests/e2e/*.test.js"]) ], - testPathIgnorePatterns: isWin - ? [] - : [ - // TODO: check why http proxy server throw error with websocket server - "/tests/e2e/allowed-hosts.test.js", - // TODO: check why this test timeout - "/tests/e2e/host.test.js", - // TODO: not support progress plugin event yet - "/tests/e2e/progress.test.js", - // TODO: check why this test throw error when run with other tests - "/tests/e2e/watch-files.test.js" - ], + testPathIgnorePatterns: + isWin || process.env.CI + ? [] + : [ + // TODO: check why http proxy server throw error with websocket server + "/tests/e2e/allowed-hosts.test.js", + // TODO: check why this test timeout + "/tests/e2e/host.test.js", + // TODO: not support progress plugin event yet + "/tests/e2e/progress.test.js", + // TODO: check why this test throw error when run with other tests + "/tests/e2e/watch-files.test.js" + ], cache: false, testTimeout: process.env.CI ? 120000 : 30000, transform: { From cdaa10b2bf38ed907a078aeb66b9641f746f5141 Mon Sep 17 00:00:00 2001 From: CaiDi Date: Tue, 10 Sep 2024 18:46:24 +0800 Subject: [PATCH 17/51] docs: add NIO logo at built with rspack (#7856) doc: add nio logo at built with rspack Co-authored-by: di.cai --- .../BuiltWithRspack/assets/nio.svg | 28 +++++++++++++++++++ .../Landingpage/BuiltWithRspack/index.tsx | 7 +++++ 2 files changed, 35 insertions(+) create mode 100644 website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg b/website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg new file mode 100644 index 00000000000..f3b4608c7c3 --- /dev/null +++ b/website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/website/theme/components/Landingpage/BuiltWithRspack/index.tsx b/website/theme/components/Landingpage/BuiltWithRspack/index.tsx index 01a1d927993..cf53c2aab71 100644 --- a/website/theme/components/Landingpage/BuiltWithRspack/index.tsx +++ b/website/theme/components/Landingpage/BuiltWithRspack/index.tsx @@ -12,6 +12,7 @@ import bytedanceLogo from './assets/bytedance.svg'; import discordLogo from './assets/discord.svg'; import intuitLogo from './assets/intuit.svg'; import microsoftLogo from './assets/microsoft.svg'; +import nioLogo from './assets/nio.svg'; type Company = { name: string; @@ -59,6 +60,12 @@ const companyList: Company[] = [ url: 'https://discord.com', width: 140, }, + { + name: 'NIO', + logo: nioLogo, + url: 'https://nio.com', + width: 115, + }, ]; const BuiltWithRspack: React.FC = memo(() => { From a95be0088b4a83e8de4ab3c8301b65d8576f2699 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Tue, 10 Sep 2024 19:21:37 +0800 Subject: [PATCH 18/51] ci: remove firefox (#7857) --- .github/workflows/reusable-build.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml index f4fb03977a8..fc86ba1b572 100644 --- a/.github/workflows/reusable-build.yml +++ b/.github/workflows/reusable-build.yml @@ -298,12 +298,6 @@ jobs: with: node-version: ${{ matrix.node }} - - name: Setup firefox - if: ${{ !contains(inputs.target, 'windows') && !inputs.skipable }} - uses: browser-actions/setup-firefox@latest - with: - firefox-version: latest - ### x86_64-unknown-linux-gnu - name: Test x86_64-unknown-linux-gnu From 0f49fafbd091c4f3bff879b1d720f771ac650f7e Mon Sep 17 00:00:00 2001 From: Hana Date: Tue, 10 Sep 2024 19:24:59 +0800 Subject: [PATCH 19/51] docs: add clarification to loader `this.callback` usage (#7855) * docs: add clarification to loader this.callback usage * Update website/docs/zh/api/loader-api/context.mdx Co-authored-by: neverland * Update website/docs/en/api/loader-api/context.mdx Co-authored-by: neverland --------- Co-authored-by: neverland --- website/docs/en/api/loader-api/context.mdx | 7 +++++++ website/docs/zh/api/loader-api/context.mdx | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/website/docs/en/api/loader-api/context.mdx b/website/docs/en/api/loader-api/context.mdx index 3f1154d1adb..df2b63ec87f 100644 --- a/website/docs/en/api/loader-api/context.mdx +++ b/website/docs/en/api/loader-api/context.mdx @@ -70,6 +70,13 @@ A function that can be called synchronously or asynchronously in order to return 3. The third parameter is a source map that can be processed by the loader. 4. The fourth parameter is ignored by Rspack and can be anything (e.g. some metadata). +:::warning +In case this function is called, you should return `undefined` to avoid ambiguous loader results. + +The value passed to `this.callback` will be passed to the next loader in the chain. +The `sourceMap` and `meta` parameters are optional. If they are not passed, the next loader will not receive them. +::: + ## this.clearDependencies() ```ts diff --git a/website/docs/zh/api/loader-api/context.mdx b/website/docs/zh/api/loader-api/context.mdx index 42500c64db8..6a538e3a63e 100644 --- a/website/docs/zh/api/loader-api/context.mdx +++ b/website/docs/zh/api/loader-api/context.mdx @@ -73,6 +73,13 @@ function callback( 第一个参数必须是 `Error` 或者 `null`,会标记当前模块为编译失败,第二个参数是一个 `string` 或者 `Buffer`,表示模块被该 Loader 处理后的文件内容,第三个参数是一个可以该 Loader 处理后的 source map,第四个参数会被 Rspack 忽略,可以是任何东西(例如一些元数据)。 +:::warning +当这个函数被调用时,你应该返回 `undefined` 以避免 loader 结果的歧义。 + +传递给 `this.callback` 的值会传递给下一个 loader。 +`sourceMap` 和 `meta` 参数是可选的,如果没有传递,那么下一个 loader 将不会收到它们。 +::: + ## this.context 当前模块所在的目录。 From 700be1d9b9283eef664347ea5a820a5c5983fc94 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:03:00 +0800 Subject: [PATCH 20/51] release:1.0.4 (#7844) Release Packages:1.0.4 Co-authored-by: github-actions[bot] --- crates/node_binding/package.json | 2 +- npm/darwin-arm64/package.json | 2 +- npm/darwin-x64/package.json | 2 +- npm/linux-x64-gnu/package.json | 2 +- npm/win32-x64-msvc/package.json | 2 +- package.json | 2 +- packages/create-rspack/package.json | 2 +- packages/rspack-cli/package.json | 2 +- packages/rspack-dev-server/package.json | 16 ++++++++-------- packages/rspack-test-tools/package.json | 2 +- packages/rspack/package.json | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/node_binding/package.json b/crates/node_binding/package.json index e02e301bec0..14137d0c1ce 100644 --- a/crates/node_binding/package.json +++ b/crates/node_binding/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Node binding for rspack", "main": "binding.js", diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json index 29bef42ae3d..e4257c870a8 100644 --- a/npm/darwin-arm64/package.json +++ b/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-darwin-arm64", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.darwin-arm64.node", diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json index 8146a151e26..95defe95b1c 100644 --- a/npm/darwin-x64/package.json +++ b/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-darwin-x64", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.darwin-x64.node", diff --git a/npm/linux-x64-gnu/package.json b/npm/linux-x64-gnu/package.json index 3960a844efe..5c8653efbcf 100644 --- a/npm/linux-x64-gnu/package.json +++ b/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-linux-x64-gnu", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.linux-x64-gnu.node", diff --git a/npm/win32-x64-msvc/package.json b/npm/win32-x64-msvc/package.json index d7d828bf69c..b30f6cff6c8 100644 --- a/npm/win32-x64-msvc/package.json +++ b/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-win32-x64-msvc", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.win32-x64-msvc.node", diff --git a/package.json b/package.json index 26c18339839..475113f144c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monorepo", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "The fast Rust-based web bundler with webpack-compatible API", "private": true, diff --git a/packages/create-rspack/package.json b/packages/create-rspack/package.json index 2ed7d21f829..02019cac575 100644 --- a/packages/create-rspack/package.json +++ b/packages/create-rspack/package.json @@ -1,6 +1,6 @@ { "name": "create-rspack", - "version": "1.0.3", + "version": "1.0.4", "homepage": "https://rspack.dev", "bugs": "https://github.com/web-infra-dev/rspack/issues", "repository": { diff --git a/packages/rspack-cli/package.json b/packages/rspack-cli/package.json index 2119eaa525a..c337958276e 100644 --- a/packages/rspack-cli/package.json +++ b/packages/rspack-cli/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/cli", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "CLI for rspack", "publishConfig": { diff --git a/packages/rspack-dev-server/package.json b/packages/rspack-dev-server/package.json index f5c6976e1f9..3060392f170 100644 --- a/packages/rspack-dev-server/package.json +++ b/packages/rspack-dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/dev-server", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Development server for rspack", "main": "./dist/index.js", @@ -43,14 +43,14 @@ "@types/express": "4.17.21", "@types/mime-types": "2.1.4", "@types/ws": "8.5.10", + "graceful-fs": "4.2.10", + "http-proxy": "^1.18.1", "jest-serializer-path": "^0.1.15", - "typescript": "5.0.2", - "tcp-port-used": "^1.0.2", "puppeteer": "^23.2.2", - "supertest": "^6.1.3", "sockjs-client": "^1.6.1", - "graceful-fs": "4.2.10", - "http-proxy": "^1.18.1" + "supertest": "^6.1.3", + "tcp-port-used": "^1.0.2", + "typescript": "5.0.2" }, "dependencies": { "chokidar": "^3.6.0", @@ -58,10 +58,10 @@ "express": "^4.19.2", "http-proxy-middleware": "^2.0.6", "mime-types": "^2.1.35", + "p-retry": "4.6.2", "webpack-dev-middleware": "^7.4.2", "webpack-dev-server": "5.0.4", - "ws": "^8.16.0", - "p-retry": "4.6.2" + "ws": "^8.16.0" }, "peerDependencies": { "@rspack/core": "*" diff --git a/packages/rspack-test-tools/package.json b/packages/rspack-test-tools/package.json index 546b81046e8..e33e062458d 100644 --- a/packages/rspack-test-tools/package.json +++ b/packages/rspack-test-tools/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/test-tools", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "description": "Test tools for rspack", "main": "dist/index.js", diff --git a/packages/rspack/package.json b/packages/rspack/package.json index f5a021e4fa4..cf5dfba2688 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/core", - "version": "1.0.3", + "version": "1.0.4", "webpackVersion": "5.75.0", "license": "MIT", "description": "The fast Rust-based web bundler with webpack-compatible API", From b1b5504c93291a1f55ea7caee47e3a6c47c50a25 Mon Sep 17 00:00:00 2001 From: Wei Date: Tue, 10 Sep 2024 20:06:51 +0800 Subject: [PATCH 21/51] fix(external_module): use "const" if supported when concatenation (#7836) --- crates/rspack_core/src/external_module.rs | 36 +++++++++++-------- .../request/output.snap.txt | 2 +- .../resource/output.snap.txt | 2 +- .../resource/output.snap.txt | 2 +- 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/crates/rspack_core/src/external_module.rs b/crates/rspack_core/src/external_module.rs index 54a1ce944af..4576a5d1c64 100644 --- a/crates/rspack_core/src/external_module.rs +++ b/crates/rspack_core/src/external_module.rs @@ -71,10 +71,17 @@ impl ExternalRequestValue { } } -fn get_namespace_object_export(concatenation_scope: Option<&mut ConcatenationScope>) -> Cow { +fn get_namespace_object_export( + concatenation_scope: Option<&mut ConcatenationScope>, + supports_const: bool, +) -> Cow { if let Some(concatenation_scope) = concatenation_scope { concatenation_scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); - format!("var {NAMESPACE_OBJECT_EXPORT}").into() + format!( + "{} {NAMESPACE_OBJECT_EXPORT}", + if supports_const { "const" } else { "var" } + ) + .into() } else { "module.exports".into() } @@ -188,21 +195,22 @@ impl ExternalModule { ) -> Result<(BoxSource, ChunkInitFragments, RuntimeGlobals)> { let mut chunk_init_fragments: ChunkInitFragments = Default::default(); let mut runtime_requirements: RuntimeGlobals = Default::default(); + let supports_const = compilation.options.output.environment.supports_const(); let source = match self.external_type.as_str() { "this" if let Some(request) = request => format!( "{} = (function() {{ return {}; }}());", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_global_variable_external(request, external_type) ), "window" | "self" if let Some(request) = request => format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_global_variable_external(request, external_type) ), "global" if let Some(request) = request => format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_global_variable_external(request, &compilation.options.output.global_object) ), "commonjs" | "commonjs2" | "commonjs-module" | "commonjs-static" @@ -210,7 +218,7 @@ impl ExternalModule { { format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_commonjs(request) ) } @@ -229,14 +237,14 @@ impl ExternalModule { ); format!( "{} = __WEBPACK_EXTERNAL_createRequire({}.url)({});", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), compilation.options.output.import_meta_name, json_stringify(request.primary()) ) } else { format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_commonjs(request) ) } @@ -249,7 +257,7 @@ impl ExternalModule { .unwrap_or_default(); format!( "{} = __WEBPACK_EXTERNAL_MODULE_{}__;", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), to_identifier(id) ) } @@ -258,7 +266,7 @@ impl ExternalModule { "import" => { format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_import(request, compilation) ) } @@ -283,13 +291,13 @@ impl ExternalModule { r#" {} = __WEBPACK_EXTERNAL_MODULE_{}__; "#, - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), id.clone() ) } else { format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_import(request, compilation) ) } @@ -302,7 +310,7 @@ impl ExternalModule { } "var" | "promise" | "const" | "let" | "assign" if let Some(request) = request => format!( "{} = {};", - get_namespace_object_export(concatenation_scope), + get_namespace_object_export(concatenation_scope, supports_const), get_source_for_default_case(false, request) ), "script" if let Some(request) = request => { @@ -325,7 +333,7 @@ if(typeof {global} !== "undefined") return resolve(); }}, {global_str}); }}).then(function() {{ return {global}; }}); "#, - export = get_namespace_object_export(concatenation_scope), + export = get_namespace_object_export(concatenation_scope, supports_const), global = url_and_global.global, global_str = serde_json::to_string(url_and_global.global).map_err(|e| error!(e.to_string()))?, diff --git a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/request/output.snap.txt b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/request/output.snap.txt index 347659048a1..806c3cf0c26 100644 --- a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/request/output.snap.txt +++ b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/request/output.snap.txt @@ -82,7 +82,7 @@ var a_default = /*#__PURE__*/__webpack_require__.n(a); var c = __webpack_require__("../../normalModuleFactory​#afterResolve/request/c.js"); var c_default = /*#__PURE__*/__webpack_require__.n(c); ;// CONCATENATED MODULE: external "fs" -var external_fs_namespaceObject = require("fs"); +const external_fs_namespaceObject = require("fs"); var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_namespaceObject); ;// CONCATENATED MODULE: ../../normalModuleFactory​#afterResolve/request/request.js diff --git a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/resource/output.snap.txt b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/resource/output.snap.txt index 5ae4c7c9e7c..663054f183e 100644 --- a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/resource/output.snap.txt +++ b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#afterResolve/resource/output.snap.txt @@ -82,7 +82,7 @@ var a_default = /*#__PURE__*/__webpack_require__.n(a); var b = __webpack_require__("../../normalModuleFactory​#afterResolve/resource/b.js"); var b_default = /*#__PURE__*/__webpack_require__.n(b); ;// CONCATENATED MODULE: external "fs" -var external_fs_namespaceObject = require("fs"); +const external_fs_namespaceObject = require("fs"); var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_namespaceObject); ;// CONCATENATED MODULE: ../../normalModuleFactory​#afterResolve/resource/resource.js diff --git a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#resolve/resource/output.snap.txt b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#resolve/resource/output.snap.txt index 18fc0f6d7d6..b61042c87a2 100644 --- a/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#resolve/resource/output.snap.txt +++ b/packages/rspack-test-tools/tests/hookCases/normalModuleFactory#resolve/resource/output.snap.txt @@ -82,7 +82,7 @@ var a_default = /*#__PURE__*/__webpack_require__.n(a); var b = __webpack_require__("../../normalModuleFactory​#resolve/resource/b.js"); var b_default = /*#__PURE__*/__webpack_require__.n(b); ;// CONCATENATED MODULE: external "fs" -var external_fs_namespaceObject = require("fs"); +const external_fs_namespaceObject = require("fs"); var external_fs_default = /*#__PURE__*/__webpack_require__.n(external_fs_namespaceObject); ;// CONCATENATED MODULE: ../../normalModuleFactory​#resolve/resource/resource.js From f5c6a1bd21b4c52c0fbc208f909039e8f47ff805 Mon Sep 17 00:00:00 2001 From: Soon Date: Tue, 10 Sep 2024 20:15:08 +0800 Subject: [PATCH 22/51] refactor(types): remove @ts-expect-error in Compiler and MultiCompiler (#7824) * chore: update * chore: update * chore: update --- .../tests/errorCases/error-test-push.js | 2 +- .../tests/errorCases/error-test-shift.js | 2 +- .../tests/errorCases/error-test-splice-1.js | 2 +- .../tests/errorCases/error-test-splice-2.js | 2 +- .../tests/errorCases/warning-test-push.js | 2 +- .../tests/errorCases/warning-test-shift.js | 2 +- .../tests/errorCases/warning-test-splice-1.js | 2 +- .../tests/errorCases/warning-test-splice-2.js | 2 +- packages/rspack/etc/api.md | 16 ++-- packages/rspack/src/Compiler.ts | 32 ++++---- packages/rspack/src/MultiCompiler.ts | 73 +++++++++---------- packages/rspack/src/MultiWatching.ts | 19 +++-- packages/rspack/src/Watching.ts | 35 ++++----- .../src/builtin-plugin/css-extract/index.ts | 11 +-- packages/rspack/src/config/defaults.ts | 7 +- .../rspack/src/node/NodeWatchFileSystem.ts | 6 +- packages/rspack/src/node/nodeConsole.ts | 5 +- packages/rspack/src/util/comparators.ts | 3 +- tests/plugin-test/copy-plugin/build/main.js | 2 +- 19 files changed, 105 insertions(+), 120 deletions(-) diff --git a/packages/rspack-test-tools/tests/errorCases/error-test-push.js b/packages/rspack-test-tools/tests/errorCases/error-test-push.js index c37f7b0e3ec..0d2e6280c76 100644 --- a/packages/rspack-test-tools/tests/errorCases/error-test-push.js +++ b/packages/rspack-test-tools/tests/errorCases/error-test-push.js @@ -20,7 +20,7 @@ module.exports = { Object { "message": " × Error: test push\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test push\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-push.js:10:31)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test push\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-push.js:10:31)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, Object { "loc": "1:0-33", diff --git a/packages/rspack-test-tools/tests/errorCases/error-test-shift.js b/packages/rspack-test-tools/tests/errorCases/error-test-shift.js index bddeafee97b..b1a1123a1ba 100644 --- a/packages/rspack-test-tools/tests/errorCases/error-test-shift.js +++ b/packages/rspack-test-tools/tests/errorCases/error-test-shift.js @@ -24,7 +24,7 @@ module.exports = { Object { "message": " × Error: test unshift\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test unshift\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-shift.js:13:35)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test unshift\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-shift.js:13:35)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, ], "warnings": Array [], diff --git a/packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js b/packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js index 0d3093c077d..1faf81377c2 100644 --- a/packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js +++ b/packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js @@ -20,7 +20,7 @@ module.exports = { Object { "message": " × Error: test splice\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js:10:39)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-splice-1.js:10:39)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, ], "warnings": Array [], diff --git a/packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js b/packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js index d269b3a4a21..5f6557be93c 100644 --- a/packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js +++ b/packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js @@ -20,7 +20,7 @@ module.exports = { Object { "message": " × Error: test splice\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js:10:39)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/error-test-splice-2.js:10:39)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, Object { "loc": "1:0-33", diff --git a/packages/rspack-test-tools/tests/errorCases/warning-test-push.js b/packages/rspack-test-tools/tests/errorCases/warning-test-push.js index 4f854cc77dd..4c78c6d000f 100644 --- a/packages/rspack-test-tools/tests/errorCases/warning-test-push.js +++ b/packages/rspack-test-tools/tests/errorCases/warning-test-push.js @@ -21,7 +21,7 @@ module.exports = { Object { "message": " ⚠ Error: test push\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test push\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-push.js:10:33)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test push\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-push.js:10:33)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, Object { "message": " ⚠ Module parse warning:\\n ╰─▶ ⚠ Unsupported feature: require.main.require() is not supported by Rspack.\\n ╭────\\n 1 │ require.main.require('./file');\\n · ──────────────────────────────\\n ╰────\\n \\n", diff --git a/packages/rspack-test-tools/tests/errorCases/warning-test-shift.js b/packages/rspack-test-tools/tests/errorCases/warning-test-shift.js index 6fab4fb139a..9739769cf70 100644 --- a/packages/rspack-test-tools/tests/errorCases/warning-test-shift.js +++ b/packages/rspack-test-tools/tests/errorCases/warning-test-shift.js @@ -25,7 +25,7 @@ module.exports = { Object { "message": " ⚠ Error: test unshift\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test unshift\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-shift.js:13:37)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test unshift\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-shift.js:13:37)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, ], } diff --git a/packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js b/packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js index 54c8fa2be9d..8c3fcd5d5fe 100644 --- a/packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js +++ b/packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js @@ -21,7 +21,7 @@ module.exports = { Object { "message": " ⚠ Error: test splice\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js:10:41)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-splice-1.js:10:41)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, ], } diff --git a/packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js b/packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js index c734d79eecc..eea06514979 100644 --- a/packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js +++ b/packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js @@ -21,7 +21,7 @@ module.exports = { Object { "message": " ⚠ Error: test splice\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n │ at xxx\\n", "moduleTrace": Array [], - "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js:10:41)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:460:41\\n at packages/rspack/dist/Compiler.js:527:23", + "stack": "Error: test splice\\n at Object.fn (packages/rspack-test-tools/tests/errorCases/warning-test-splice-2.js:10:41)\\n at next (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:523:25)\\n at AsyncSeriesHook.callAsyncStageRange (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:543:9)\\n at AsyncSeriesHook.callAsync (node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js:82:21)\\n at packages/rspack/dist/Compiler.js:458:41\\n at packages/rspack/dist/Compiler.js:525:23", }, Object { "message": " ⚠ Module parse warning:\\n ╰─▶ ⚠ Unsupported feature: require.main.require() is not supported by Rspack.\\n ╭────\\n 1 │ require.main.require('./file');\\n · ──────────────────────────────\\n ╰────\\n \\n", diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 51ee7a0da6a..1de9e310a5d 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -54,7 +54,7 @@ import * as liteTapable from '@rspack/lite-tapable'; import { Logger as Logger_2 } from './logging/Logger'; import type { ModuleDTO } from '@rspack/binding'; import { RawCopyPattern } from '@rspack/binding'; -import type { RawCssExtractPluginOption } from '@rspack/binding'; +import { RawCssExtractPluginOption } from '@rspack/binding'; import type { RawFuncUseCtx } from '@rspack/binding'; import { RawIgnorePluginOptions } from '@rspack/binding'; import { RawOptions } from '@rspack/binding'; @@ -7269,11 +7269,11 @@ export class MultiStats { class MultiWatching { constructor(watchings: Watching[], compiler: MultiCompiler); // (undocumented) - close(callback: any): void; + close(callback: Callback): void; // (undocumented) compiler: MultiCompiler; // (undocumented) - invalidate(callback: any): void; + invalidate(callback: Callback): void; // (undocumented) resume(): void; // (undocumented) @@ -15733,7 +15733,7 @@ interface WatchFileSystem { // @public (undocumented) export class Watching { - constructor(compiler: Compiler, watchOptions: WatchOptions, handler: (error?: Error, stats?: Stats) => void); + constructor(compiler: Compiler, watchOptions: WatchOptions, handler: Callback); // (undocumented) blocked: boolean; // (undocumented) @@ -15743,21 +15743,21 @@ export class Watching { // (undocumented) compiler: Compiler; // (undocumented) - handler: (error?: Error, stats?: Stats) => void; + handler: Callback; // (undocumented) invalid: boolean; // (undocumented) invalidate(callback?: Callback): void; // (undocumented) - isBlocked?: () => boolean; + isBlocked: () => boolean; // (undocumented) lastWatcherStartTime: number; // (undocumented) lazyCompilationInvalidate(files: Set): void; // (undocumented) - onChange?: () => void; + onChange: () => void; // (undocumented) - onInvalid?: () => void; + onInvalid: () => void; // (undocumented) pausedWatcher?: Watcher; // (undocumented) diff --git a/packages/rspack/src/Compiler.ts b/packages/rspack/src/Compiler.ts index b5264588be6..adf5e8899ed 100644 --- a/packages/rspack/src/Compiler.ts +++ b/packages/rspack/src/Compiler.ts @@ -62,7 +62,8 @@ import type { EntryNormalized, OutputNormalized, RspackOptionsNormalized, - RspackPluginInstance + RspackPluginInstance, + WatchOptions } from "./config"; import type { InputFileSystem, @@ -328,8 +329,7 @@ class Compiler { let normalizedChildName = childName; if (typeof normalizedName === "function") { if (typeof normalizedChildName === "function") { - // @ts-expect-error - return this.getInfrastructureLogger(_ => { + return this.getInfrastructureLogger(() => { if (typeof normalizedName === "function") { normalizedName = normalizedName(); if (!normalizedName) { @@ -391,13 +391,14 @@ class Compiler { handler: liteTapable.Callback ): Watching { if (this.running) { - // @ts-expect-error - return handler(new ConcurrentCompilationError()); + // cannot be resolved without assertion + // copy from webpack + // Type 'void' is not assignable to type 'Watching'. + return handler(new ConcurrentCompilationError()) as unknown as Watching; } this.running = true; this.watchMode = true; - // @ts-expect-error - this.watching = new Watching(this, watchOptions, handler); + this.watching = new Watching(this, watchOptions as WatchOptions, handler); return this.watching; } @@ -411,8 +412,7 @@ class Compiler { const startTime = Date.now(); this.running = true; const doRun = () => { - // @ts-expect-error - const finalCallback = (err, stats?) => { + const finalCallback = (err: Error | null, stats?: Stats) => { this.idle = true; this.cache.beginIdle(); this.idle = true; @@ -423,7 +423,7 @@ class Compiler { if (callback) { callback(err, stats); } - this.hooks.afterDone.call(stats); + this.hooks.afterDone.call(stats!); }; this.hooks.beforeRun.callAsync(this, err => { if (err) { @@ -583,11 +583,13 @@ class Compiler { ) ]; - for (const name in this.hooks) { - if (canInherentFromParent(name as keyof Compiler["hooks"])) { - //@ts-ignore + for (const hookName in this.hooks) { + type HookNames = keyof Compiler["hooks"]; + + const name = hookName as unknown as HookNames; + + if (canInherentFromParent(name)) { if (childCompiler.hooks[name]) { - //@ts-ignore childCompiler.hooks[name].taps = this.hooks[name].taps.slice(); } } @@ -1313,7 +1315,7 @@ class Compiler { } } if (this.#nonSkippableRegisters.join() !== kinds.join()) { - this.#getInstance((error, instance) => { + this.#getInstance((_error, instance) => { instance!.setNonSkippableRegisters(kinds); this.#nonSkippableRegisters = kinds; }); diff --git a/packages/rspack/src/MultiCompiler.ts b/packages/rspack/src/MultiCompiler.ts index bbca1044531..fb7a8863c46 100644 --- a/packages/rspack/src/MultiCompiler.ts +++ b/packages/rspack/src/MultiCompiler.ts @@ -205,7 +205,8 @@ export class MultiCompiler { validateDependencies( callback: liteTapable.Callback ): boolean { - const edges = new Set<{ source: Compiler; target: Compiler }>(); + type Edge = { source: Compiler; target: Compiler }; + const edges = new Set(); const missing: string[] = []; const targetFound = (compiler: Compiler) => { for (const edge of edges) { @@ -215,11 +216,10 @@ export class MultiCompiler { } return false; }; - // @ts-expect-error - const sortEdges = (e1, e2) => { + const sortEdges = (e1: Edge, e2: Edge) => { return ( - e1.source.name.localeCompare(e2.source.name) || - e1.target.name.localeCompare(e2.target.name) + e1.source.name!.localeCompare(e2.source.name!) || + e1.target.name!.localeCompare(e2.target.name!) ); }; for (const source of this.compilers) { @@ -238,8 +238,9 @@ export class MultiCompiler { } } } - /** @type {string[]} */ - const errors = missing.map(m => `Compiler dependency \`${m}\` not found.`); + const errors: string[] = missing.map( + m => `Compiler dependency \`${m}\` not found.` + ); const stack = this.compilers.filter(c => !targetFound(c)); while (stack.length > 0) { const current = stack.pop(); @@ -254,8 +255,7 @@ export class MultiCompiler { } } if (edges.size > 0) { - /** @type {string[]} */ - const lines = Array.from(edges) + const lines: string[] = Array.from(edges) .sort(sortEdges) .map(edge => `${edge.source.name} -> ${edge.target.name}`); lines.unshift("Circular dependency found in compiler dependencies."); @@ -292,8 +292,6 @@ export class MultiCompiler { ) => void, callback: liteTapable.Callback ): SetupResult[] { - /** @typedef {{ compiler: Compiler, setupResult: SetupResult, result: Stats, state: "pending" | "blocked" | "queued" | "starting" | "running" | "running-outdated" | "done", children: Node[], parents: Node[] }} Node */ - // State transitions for nodes: // -> blocked (initial) // blocked -> starting [running++] (when all parents done) @@ -336,13 +334,12 @@ export class MultiCompiler { let errored = false; let running = 0; const parallelism = this._options.parallelism!; - /** - * @param {Node} node node - * @param {Error=} err error - * @param {Stats=} stats result - * @returns {void} - */ - const nodeDone = (node: Node, err: Error, stats: Stats) => { + + const nodeDone = ( + node: Node, + err: Error, + stats: Stats + ): void => { if (errored) return; if (err) { errored = true; @@ -375,7 +372,7 @@ export class MultiCompiler { * @param {Node} node node * @returns {void} */ - const nodeInvalidFromParent = (node: Node) => { + const nodeInvalidFromParent = (node: Node): void => { if (node.state === "done") { node.state = "blocked"; } else if (node.state === "running") { @@ -389,7 +386,7 @@ export class MultiCompiler { * @param {Node} node node * @returns {void} */ - const nodeInvalid = (node: Node) => { + const nodeInvalid = (node: Node): void => { if (node.state === "done") { node.state = "pending"; } else if (node.state === "running") { @@ -403,8 +400,7 @@ export class MultiCompiler { * @param {Node} node node * @returns {void} */ - // @ts-expect-error - const nodeChange = node => { + const nodeChange = (node: Node): void => { nodeInvalid(node); if (node.state === "pending") { node.state = "blocked"; @@ -421,8 +417,7 @@ export class MultiCompiler { (node.setupResult = setup( node.compiler, i, - // @ts-expect-error - nodeDone.bind(null, node), + nodeDone.bind(null, node) as liteTapable.Callback, () => node.state !== "starting" && node.state !== "running", () => nodeChange(node), () => nodeInvalid(node) @@ -445,8 +440,11 @@ export class MultiCompiler { ) { running++; node.state = "starting"; - // @ts-expect-error - run(node.compiler, node.setupResult!, nodeDone.bind(null, node)); + run( + node.compiler, + node.setupResult!, + nodeDone.bind(null, node) as liteTapable.Callback + ); node.state = "running"; } } @@ -489,18 +487,15 @@ export class MultiCompiler { if (this.validateDependencies(handler)) { const watchings = this.#runGraph( - // @ts-expect-error - (compiler: Compiler, idx, done, isBlocked, setChanged, setInvalid) => { - const watching = compiler.watch( - // @ts-expect-error - Array.isArray(watchOptions) ? watchOptions[idx] : watchOptions, - // @ts-expect-error - done + (compiler, idx, done, isBlocked, setChanged, setInvalid) => { + const watching = compiler!.watch( + Array.isArray(watchOptions) ? watchOptions[idx!] : watchOptions, + done! ); if (watching) { - watching.onInvalid = setInvalid; - watching.onChange = setChanged; - watching.isBlocked = isBlocked; + watching.onInvalid = setInvalid!; + watching.onChange = setChanged!; + watching.isBlocked = isBlocked!; } return watching; }, @@ -510,7 +505,6 @@ export class MultiCompiler { }, handler ); - // @ts-expect-error return new MultiWatching(watchings, this); } @@ -550,8 +544,9 @@ export class MultiCompiler { (compiler, cb) => { compiler.close(cb); }, - // @ts-expect-error - callback + // cannot be resolved without assertion + // Type 'Error | null | undefined' is not assignable to type 'Error | null' + callback as (err: Error | null | undefined) => void ); } } diff --git a/packages/rspack/src/MultiWatching.ts b/packages/rspack/src/MultiWatching.ts index 05c934d4cdd..3a245eec651 100644 --- a/packages/rspack/src/MultiWatching.ts +++ b/packages/rspack/src/MultiWatching.ts @@ -10,6 +10,7 @@ import asyncLib from "neo-async"; +import type { Callback } from "@rspack/lite-tapable"; import type { MultiCompiler } from "./MultiCompiler"; import type { Watching } from "./Watching"; @@ -25,13 +26,14 @@ class MultiWatching { this.watchings = watchings; this.compiler = compiler; } - // @ts-expect-error - invalidate(callback) { + invalidate(callback: Callback) { if (callback) { asyncLib.each( this.watchings, (watching, callback) => watching.invalidate(callback), - callback + // cannot be resolved without assertion + // Type 'Error | null | undefined' is not assignable to type 'Error | null' + callback as (err: Error | null | undefined) => void ); } else { for (const watching of this.watchings) { @@ -40,12 +42,7 @@ class MultiWatching { } } - /** - * @param {Callback} callback signals when the watcher is closed - * @returns {void} - */ - // @ts-expect-error - close(callback) { + close(callback: Callback) { asyncLib.forEach( this.watchings, (watching, finishedCallback) => { @@ -55,7 +52,9 @@ class MultiWatching { this.compiler.hooks.watchClose.call(); if (typeof callback === "function") { this.compiler.running = false; - callback(err); + // cannot be resolved without assertion + // Type 'Error | null | undefined' is not assignable to type 'Error | null' + callback(err as Error | null); } } ); diff --git a/packages/rspack/src/Watching.ts b/packages/rspack/src/Watching.ts index d01015b33ad..e96d4bc6b21 100644 --- a/packages/rspack/src/Watching.ts +++ b/packages/rspack/src/Watching.ts @@ -19,20 +19,20 @@ export class Watching { watcher?: Watcher; pausedWatcher?: Watcher; compiler: Compiler; - handler: (error?: Error, stats?: Stats) => void; + handler: Callback; callbacks: Callback[]; watchOptions: WatchOptions; - // @ts-expect-error + // @ts-expect-error lastWatcherStartTime will be assigned with Date.now() during initialization lastWatcherStartTime: number; running: boolean; blocked: boolean; - isBlocked?: () => boolean; - onChange?: () => void; - onInvalid?: () => void; + isBlocked: () => boolean; + onChange: () => void; + onInvalid: () => void; invalid: boolean; startTime?: number; #invalidReported: boolean; - #closeCallbacks?: ((err?: Error) => void)[]; + #closeCallbacks?: ((err?: Error | null) => void)[]; #initial: boolean; #closed: boolean; #collectedChangedFiles?: Set; @@ -42,7 +42,7 @@ export class Watching { constructor( compiler: Compiler, watchOptions: WatchOptions, - handler: (error?: Error, stats?: Stats) => void + handler: Callback ) { this.callbacks = []; this.invalid = false; @@ -103,7 +103,6 @@ export class Watching { changedFiles, removedFiles ); - // @ts-expect-error this.onChange(); }, (fileName, changeTime) => { @@ -111,7 +110,6 @@ export class Watching { this.#invalidReported = true; this.compiler.hooks.invalid.call(fileName, changeTime); } - // @ts-expect-error this.onInvalid(); } ); @@ -135,11 +133,10 @@ export class Watching { this.compiler.fileTimestamps = undefined; this.compiler.contextTimestamps = undefined; // this.compiler.fsStartTime = undefined; - const shutdown = (err: Error) => { + const shutdown = (err: Error | null) => { this.compiler.hooks.watchClose.call(); - const closeCallbacks = this.#closeCallbacks; + const closeCallbacks = this.#closeCallbacks!; this.#closeCallbacks = undefined; - // @ts-expect-error for (const cb of closeCallbacks) cb(err); }; // TODO: compilation parameter support @@ -156,7 +153,6 @@ export class Watching { // } else { // shutdown(err); // } - // @ts-expect-error shutdown(err); }; @@ -192,7 +188,6 @@ export class Watching { this.#invalidReported = true; this.compiler.hooks.invalid.call(null, Date.now()); } - // @ts-expect-error this.onChange(); this.#invalidate(); } @@ -207,9 +202,7 @@ export class Watching { changedFiles?: Set, removedFiles?: Set ) { - // @ts-expect-error this.#mergeWithCollected(changedFiles, removedFiles); - // @ts-expect-error if (this.suspended || (this.isBlocked() && (this.blocked = true))) { return; } @@ -329,7 +322,6 @@ export class Watching { this.compiler.hooks.done.callAsync(stats, err => { if (err) return handleError(err, cbs); - // @ts-expect-error this.handler(null, stats); process.nextTick(() => { @@ -347,22 +339,21 @@ export class Watching { } #mergeWithCollected( - changedFiles: ReadonlySet, - removedFiles: ReadonlySet + changedFiles?: ReadonlySet, + removedFiles?: ReadonlySet ) { if (!changedFiles) return; - if (!this.#collectedChangedFiles) { + if (!removedFiles) return; + if (!this.#collectedChangedFiles || !this.#collectedRemovedFiles) { this.#collectedChangedFiles = new Set(changedFiles); this.#collectedRemovedFiles = new Set(removedFiles); } else { for (const file of changedFiles) { this.#collectedChangedFiles.add(file); - // @ts-expect-error this.#collectedRemovedFiles.delete(file); } for (const file of removedFiles) { this.#collectedChangedFiles.delete(file); - // @ts-expect-error this.#collectedRemovedFiles.add(file); } } diff --git a/packages/rspack/src/builtin-plugin/css-extract/index.ts b/packages/rspack/src/builtin-plugin/css-extract/index.ts index e23524c7174..34bcb0de561 100644 --- a/packages/rspack/src/builtin-plugin/css-extract/index.ts +++ b/packages/rspack/src/builtin-plugin/css-extract/index.ts @@ -1,4 +1,7 @@ -import type { RawCssExtractPluginOption } from "@rspack/binding"; +import { + BuiltinPluginName, + type RawCssExtractPluginOption +} from "@rspack/binding"; import type { Compiler } from "../.."; import { MODULE_TYPE } from "./loader"; @@ -53,8 +56,7 @@ export class CssExtractRspackPlugin { } compiler.__internal__registerBuiltinPlugin({ - // @ts-expect-error CssExtractRspackPlugin is a constant value of BuiltinPlugin - name: "CssExtractRspackPlugin", + name: BuiltinPluginName.CssExtractRspackPlugin, options: this.normalizeOptions(this.options) }); } @@ -112,8 +114,7 @@ export class CssExtractRspackPlugin { JSON.stringify(k), JSON.stringify(options.attributes![k as string]) ]) - .reduce((obj, [k, v]) => { - // @ts-expect-error + .reduce((obj: Record, [k, v]) => { obj[k] = v; return obj; }, {}) as Record) diff --git a/packages/rspack/src/config/defaults.ts b/packages/rspack/src/config/defaults.ts index 9af1d263de8..821d74078c2 100644 --- a/packages/rspack/src/config/defaults.ts +++ b/packages/rspack/src/config/defaults.ts @@ -121,10 +121,10 @@ export const applyRspackOptionsDefaults = ( targetProperties }); - // @ts-expect-error F(options, "externalsType", () => { return options.output.library - ? options.output.library.type + ? // loose type 'string', actual type is "commonjs" | "var" | "commonjs2".... + (options.output.library.type as any) : options.output.module ? "module-import" : "var"; @@ -1087,8 +1087,7 @@ const A = ( if (item === "...") { if (newArray === undefined) { newArray = value.slice(0, i); - // @ts-expect-error - obj[prop] = newArray; + obj[prop] = newArray as any; } const items = factory(); if (items !== undefined) { diff --git a/packages/rspack/src/node/NodeWatchFileSystem.ts b/packages/rspack/src/node/NodeWatchFileSystem.ts index 1afb9b2b416..6260c1c8cf6 100644 --- a/packages/rspack/src/node/NodeWatchFileSystem.ts +++ b/packages/rspack/src/node/NodeWatchFileSystem.ts @@ -38,7 +38,7 @@ export default class NodeWatchFileSystem implements WatchFileSystem { startTime: number, options: Watchpack.WatchOptions, callback: ( - error: Error, + error: Error | null, fileTimeInfoEntries: Map, contextTimeInfoEntries: Map, changedFiles: Set, @@ -101,7 +101,6 @@ export default class NodeWatchFileSystem implements WatchFileSystem { const { fileTimeInfoEntries, contextTimeInfoEntries } = fetchTimeInfo(); callback( - // @ts-expect-error null, fileTimeInfoEntries, contextTimeInfoEntries, @@ -119,8 +118,7 @@ export default class NodeWatchFileSystem implements WatchFileSystem { close: () => { if (this.watcher) { this.watcher.close(); - // @ts-expect-error - this.watcher = null; + this.watcher = null as any; } }, pause: () => { diff --git a/packages/rspack/src/node/nodeConsole.ts b/packages/rspack/src/node/nodeConsole.ts index 5916fa2bcc2..ded2326e1bb 100644 --- a/packages/rspack/src/node/nodeConsole.ts +++ b/packages/rspack/src/node/nodeConsole.ts @@ -55,8 +55,9 @@ export default function ({ const writeStatusMessage = () => { if (!currentStatusMessage) return; - //@ts-expect-error Property 'columns' does not exist on type 'WritableStream'.ts(2339) - const l = stream.columns; + // cannot be resolved without assertion, copy from webpack + // Property 'columns' does not exist on type 'WritableStream'.ts(2339) + const l: number = (stream as unknown as { columns: number }).columns; const args = l ? truncateArgs(currentStatusMessage, l - 1) : currentStatusMessage; diff --git a/packages/rspack/src/util/comparators.ts b/packages/rspack/src/util/comparators.ts index 8832cb73f2a..f4a97e2cc0f 100644 --- a/packages/rspack/src/util/comparators.ts +++ b/packages/rspack/src/util/comparators.ts @@ -90,8 +90,7 @@ export const compareChunkGroupsByIndex = ( a: ChunkGroup, b: ChunkGroup ): -1 | 0 | 1 => { - //@ts-expect-error copy from webpack - return a.index < b.index ? -1 : 1; + return a.index! < b.index! ? -1 : 1; }; const compareSelectCache: TwoKeyWeakMap< diff --git a/tests/plugin-test/copy-plugin/build/main.js b/tests/plugin-test/copy-plugin/build/main.js index 7424d448bb3..fc19c4b0817 100644 --- a/tests/plugin-test/copy-plugin/build/main.js +++ b/tests/plugin-test/copy-plugin/build/main.js @@ -1 +1 @@ -(()=>{"use strict";var r={},t={};function e(i){var c=t[i];if(void 0!==c)return c.exports;var o=t[i]={exports:{}};return r[i](o,o.exports,e),o.exports}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(r){if("object"==typeof window)return window}}(),e.rv=function(){return"1.0.0-rc.0"},(()=>{e.g.importScripts&&(r=e.g.location+"");var r,t=e.g.document;if(!r&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(r=t.currentScript.src),!r)){var i=t.getElementsByTagName("script");if(i.length){for(var c=i.length-1;c>-1&&(!r||!/^http(s?):/.test(r));)r=i[c--].src}}if(!r)throw Error("Automatic publicPath is not supported in this browser");r=r.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=r})(),e.ruid="bundler=rspack@1.0.0-rc.0",e.p})(); \ No newline at end of file +(()=>{"use strict";var r={},t={};function e(i){var o=t[i];if(void 0!==o)return o.exports;var n=t[i]={exports:{}};return r[i](n,n.exports,e),n.exports}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(r){if("object"==typeof window)return window}}(),e.rv=function(){return"1.0.3"},(()=>{e.g.importScripts&&(r=e.g.location+"");var r,t=e.g.document;if(!r&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(r=t.currentScript.src),!r)){var i=t.getElementsByTagName("script");if(i.length){for(var o=i.length-1;o>-1&&(!r||!/^http(s?):/.test(r));)r=i[o--].src}}if(!r)throw Error("Automatic publicPath is not supported in this browser");r=r.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=r})(),e.ruid="bundler=rspack@1.0.3",e.p})(); \ No newline at end of file From 930e351f4aadfc94bdc01128c5dd3f4960072aee Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 11 Sep 2024 11:48:20 +0800 Subject: [PATCH 23/51] fix(create-rspack): eslint-plugin-react-hooks version conflict (#7854) * fix(create-rspack): eslint-plugin-react-hooks version conflict * fix * fix: comment --- packages/create-rspack/package.json | 4 +- packages/create-rspack/src/index.ts | 1 - pnpm-lock.yaml | 229 +++++++--------------------- 3 files changed, 55 insertions(+), 179 deletions(-) diff --git a/packages/create-rspack/package.json b/packages/create-rspack/package.json index 02019cac575..59e0c3a34bf 100644 --- a/packages/create-rspack/package.json +++ b/packages/create-rspack/package.json @@ -24,10 +24,10 @@ "dev": "rslib build -w" }, "dependencies": { - "create-rstack": "1.0.1" + "create-rstack": "1.0.2" }, "devDependencies": { - "@rslib/core": "0.0.3", + "@rslib/core": "0.0.5", "typescript": "5.0.2" }, "publishConfig": { diff --git a/packages/create-rspack/src/index.ts b/packages/create-rspack/src/index.ts index d06252af409..c6bc4a0beba 100644 --- a/packages/create-rspack/src/index.ts +++ b/packages/create-rspack/src/index.ts @@ -1,4 +1,3 @@ -#!/usr/bin/env node import path from "node:path"; import { type Argv, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 86be8d15562..0545c1a9ee3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -137,12 +137,12 @@ importers: packages/create-rspack: dependencies: create-rstack: - specifier: 1.0.1 - version: 1.0.1 + specifier: 1.0.2 + version: 1.0.2 devDependencies: '@rslib/core': - specifier: 0.0.3 - version: 0.0.3(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(typescript@5.0.2) + specifier: 0.0.5 + version: 0.0.5(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(typescript@5.0.2) typescript: specifier: 5.0.2 version: 5.0.2 @@ -2283,6 +2283,9 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -2869,13 +2872,13 @@ packages: cpu: [x64] os: [win32] - '@rsbuild/core@1.0.1-beta.16': - resolution: {integrity: sha512-/0s6E3bKqyxUby2X4H11xQppuXpPWqZzPWkynUX19o9T2K7FuPw5cyuE0iL3f2XlGucmgftSn06Am5HsQV+A+w==} + '@rsbuild/core@1.0.1-rc.4': + resolution: {integrity: sha512-JlouV5M+azv9YP6hD11rHeUns8Yk9sQN9QmMCKhutG75j1TCEKmrL0O7UmE89+uKlJTnL5Pyzy29TLO5ncIRjg==} engines: {node: '>=16.7.0'} hasBin: true - '@rslib/core@0.0.3': - resolution: {integrity: sha512-QtLhmqBn64VvRo8/7L+Xm001XhUVgEE6PMX6KupzD4A499gBjVvk5aUgShuXOou2yLL4/tGaHNBP9x8f70zhzg==} + '@rslib/core@0.0.5': + resolution: {integrity: sha512-tVI+tgVQiIxtFztOng5l+XoO1Yd1K21ef6k6x0lmGAUfinDPgKX2s9tn93c6/dsgmu2h9e1eXA9ekQMLrRzREQ==} engines: {node: '>=16.0.0'} hasBin: true peerDependencies: @@ -2887,111 +2890,54 @@ packages: typescript: optional: true - '@rspack/binding-darwin-arm64@1.0.0-rc.0': - resolution: {integrity: sha512-4S/+q8HN69ErWUjGDePExqiNuKIEGYKEoT+91+Wz55jQV4NY1mNGRojKVKjZkz7MvbPEZ1howtpDcHuUE2+QhQ==} - cpu: [arm64] - os: [darwin] - '@rspack/binding-darwin-arm64@1.0.3': resolution: {integrity: sha512-MZlQpDRJkjIJJqmYMiziwz9vLXi1KjORYW6hemC2umDfOzUmlkRPBUF8oEqXaUQ+zYLbjhk4iTSbFdrlqUR+6w==} cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-x64@1.0.0-rc.0': - resolution: {integrity: sha512-IN96SG6yRz4JwlzuNrhdApEg4J2UI9S8uD38vrJvH9TOO/Dv1puL82EpqTYBZYfbaddZsiZG9+5LNnsU9OEXJg==} - cpu: [x64] - os: [darwin] - '@rspack/binding-darwin-x64@1.0.3': resolution: {integrity: sha512-Ke8Tw3+j5YFFIHjlqrEDnW5fbZU14s+l5LhLVaw6gVXH4yAAyFaZejaIViGi5fhKkdKmCXm0nVTK1KhhASZxAw==} cpu: [x64] os: [darwin] - '@rspack/binding-linux-arm64-gnu@1.0.0-rc.0': - resolution: {integrity: sha512-gqURooSNYGlwvgLE1xu8rz68E4Mfa2MONGTNMkre5aIYX2ZOd/MKGaB/R062Oj/78BIHmIGWcoz5GnBxBwToyw==} - cpu: [arm64] - os: [linux] - '@rspack/binding-linux-arm64-gnu@1.0.3': resolution: {integrity: sha512-Ydm6rsBnPYlKfWtz6sPRgAgJ5fQ+zFSHplR4bFlARIOXeWPn7ckQvFZrmKexuR0ULjG3Z4sbfrU6udc2MAWvig==} cpu: [arm64] os: [linux] - '@rspack/binding-linux-arm64-musl@1.0.0-rc.0': - resolution: {integrity: sha512-302IJXw7F4YpHUEVJA2bkzlNfYp26jKXIddHpG4A3diw0/+5w1C4eDGrC9kuq642kFtu4nwP/8rjP0090/Orxw==} - cpu: [arm64] - os: [linux] - '@rspack/binding-linux-arm64-musl@1.0.3': resolution: {integrity: sha512-2aS65Xne3W6qJJ5PN5oL/bMbkUeYpsQduT+ML6vY6hqvi6W6wYtkvHwscQ4HisxKQFWdB2bsE1+UT34XgHpijw==} cpu: [arm64] os: [linux] - '@rspack/binding-linux-x64-gnu@1.0.0-rc.0': - resolution: {integrity: sha512-VsHzd7iAwZA3j48PSj9beKva9wqde1BP2K3AF0aZe7amQoatG57YZtiOthZSWyDlwEm7VCuE6/4Jgf8vOQJX2g==} - cpu: [x64] - os: [linux] - '@rspack/binding-linux-x64-gnu@1.0.3': resolution: {integrity: sha512-6UWii/GBkV0B98RSjJr9Za5Y8rvU1vQpE5+8vb26pvo3Sh3kvRfOmSeIFyqR3I92er5SQKmEp8uggb74st6QGQ==} cpu: [x64] os: [linux] - '@rspack/binding-linux-x64-musl@1.0.0-rc.0': - resolution: {integrity: sha512-M6/SC33xWdlV2pmR3O3MrhfBZBpicMCl+GrJ0MXFb9ziC7auuuLfMmfqMakorphfL2yOOwSfcfZF8V2iiYoENw==} - cpu: [x64] - os: [linux] - '@rspack/binding-linux-x64-musl@1.0.3': resolution: {integrity: sha512-tpKGPJEe6vkkH3bsDz+l7xndNrOGxDvbDVOXV0uSESnRr6Pef5253Bi5PNooGLSaWQlXVDdazhi6x1beVHI2eg==} cpu: [x64] os: [linux] - '@rspack/binding-win32-arm64-msvc@1.0.0-rc.0': - resolution: {integrity: sha512-ob510ObXoIBAMjPE8iBwvhWQ8Hd4v7bJSgImUBhSGExYI+z8b/gl8tNg8y1fttLtNO8j05rj9QFsGcZmv89Zsg==} - cpu: [arm64] - os: [win32] - '@rspack/binding-win32-arm64-msvc@1.0.3': resolution: {integrity: sha512-9FwP64T6yeq3cG1JQG0VagTMuJxJCT45G9LN5RTJ2kxJ4T28vL7uEc9usPpXOyd6xpbzTKXX0mVxL+c8x0EhZQ==} cpu: [arm64] os: [win32] - '@rspack/binding-win32-ia32-msvc@1.0.0-rc.0': - resolution: {integrity: sha512-1A+3JorREQJqBcTLEY0LhGMb5B3Qlz/LmRr3K+gNBZinscZU+DKiDnZkSGdn8FuCn40E39T8V4i73mxQCqVuvQ==} - cpu: [ia32] - os: [win32] - '@rspack/binding-win32-ia32-msvc@1.0.3': resolution: {integrity: sha512-nXbeh0206bGiwV1vgY8UDk92sZ2yMvJenevnnLtGFSMTRQ46Z2f9n+mUO1GlvpanR1HAfhgPddlKasIncTJmPQ==} cpu: [ia32] os: [win32] - '@rspack/binding-win32-x64-msvc@1.0.0-rc.0': - resolution: {integrity: sha512-t6E4t668CvP+DqNDluYJMivrDtFZ0iQhUYxXd0UUhDSwXKTbGW2tOwBXGaZ3gzj9OpqWCUVJ3pqP11KiBckorw==} - cpu: [x64] - os: [win32] - '@rspack/binding-win32-x64-msvc@1.0.3': resolution: {integrity: sha512-htBi4xt+iXD/NCEo/q4fYSg5YfXymK9P9zI36NFvfguQbhwqy4JgBx0IorjDFl5qvG70sdUzY7x98DJEseGScQ==} cpu: [x64] os: [win32] - '@rspack/binding@1.0.0-rc.0': - resolution: {integrity: sha512-mx0x4ho0ndHpOnSjAEOoQZohTqJLYOl4hLEvQLEnXIPTdMjZtpiLUyuyKtzv6DrXDV5La3bsZuzbcdMyqkSWpg==} - '@rspack/binding@1.0.3': resolution: {integrity: sha512-wRLUDyi/6jFDDZJIov4uh9H9hJNk7JKDEhaMLM/5lJzzWsTLBB/q6JB1VAdIzOzBhYsU8iIMEVuG3Uih1H43uw==} - '@rspack/core@1.0.0-rc.0': - resolution: {integrity: sha512-sxS6QfVm7FbuKIYai8CyxMv5WH3MVdQmowVEqIZ/Fa8+PX17/sO5dg8tAFYAEzpCe/4dC93eYh9APr3Vc4ditQ==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@swc/helpers': '>=0.5.1' - peerDependenciesMeta: - '@swc/helpers': - optional: true - '@rspack/core@1.0.3': resolution: {integrity: sha512-BqyBSrcTtsm1EDI10TrK6tEfNsy5vEnVDnbI0HBuQBH1zhKblnvsu6Y2bbS9+OGmB+OPEr76CmuZtzb+7V3wrQ==} engines: {node: '>=16.0.0'} @@ -4463,8 +4409,8 @@ packages: caniuse-lite@1.0.30001616: resolution: {integrity: sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==} - caniuse-lite@1.0.30001654: - resolution: {integrity: sha512-wLJc602fW0OdrUR+PqsBUH3dgrjDcT+mWs/Kw86zPvgjiqOiI2TXMkBFK4KihYzZclmJxrFwgYhZDSEogFai/g==} + caniuse-lite@1.0.30001660: + resolution: {integrity: sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==} center-align@0.1.3: resolution: {integrity: sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==} @@ -4855,8 +4801,8 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - create-rstack@1.0.1: - resolution: {integrity: sha512-zx+ynJiBJc8BSrtOpDKeFv+3DJm2MOajrMoxpsHHji3vsC+BAflQo4XTcTZ+UMZrI6FKd3sk4WCtlB0bYEzONg==} + create-rstack@1.0.2: + resolution: {integrity: sha512-0zG6G4YeUewWZ/TWGuijWI5XuUrlWSScYSNaqw/Pw8qbrSEnmwLlIDAI4K0bl6DNoskcgxSRiP6YT96wWGDdtA==} cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} @@ -6967,6 +6913,9 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.9: resolution: {integrity: sha512-S1+hd+dIrC8EZqKyT9DstTH/0Z+f76kmmvZnkfQVmOpDEF9iVgdYif3Q/pIWHmCoo59bQVGW0kVL3e2nl+9+Sw==} engines: {node: '>=12'} @@ -7582,6 +7531,9 @@ packages: picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} @@ -8291,8 +8243,8 @@ packages: rrweb-cssom@0.6.0: resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - rsbuild-plugin-dts@0.0.3: - resolution: {integrity: sha512-61qs3CMkP/kWAchghCw1Oj/JQJhxXC2K/UgNKrNYdy4x6jb4x/SububXKJmGS77rNR+tztOVNHrDwX4FTL2weA==} + rsbuild-plugin-dts@0.0.5: + resolution: {integrity: sha512-YZvlc2LxmX+63DJd/HGlYyz3Pl6vBQxAWMax+Qk1gWUDZLRmOFZdo2OPmjW/xID9Jp0GeSdq95AzATH/7pAAyA==} engines: {node: '>=16.0.0'} peerDependencies: '@microsoft/api-extractor': ^7 @@ -11352,6 +11304,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -11992,90 +11946,51 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.17.2': optional: true - '@rsbuild/core@1.0.1-beta.16': + '@rsbuild/core@1.0.1-rc.4': dependencies: - '@rspack/core': 1.0.0-rc.0(@swc/helpers@0.5.12) + '@rspack/core': 1.0.3(@swc/helpers@0.5.12) '@rspack/lite-tapable': 1.0.0 '@swc/helpers': 0.5.12 - caniuse-lite: 1.0.30001654 + caniuse-lite: 1.0.30001660 core-js: 3.38.1 optionalDependencies: fsevents: 2.3.3 - '@rslib/core@0.0.3(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(typescript@5.0.2)': + '@rslib/core@0.0.5(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(typescript@5.0.2)': dependencies: - '@rsbuild/core': 1.0.1-beta.16 - rsbuild-plugin-dts: 0.0.3(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(@rsbuild/core@1.0.1-beta.16)(typescript@5.0.2) + '@rsbuild/core': 1.0.1-rc.4 + rsbuild-plugin-dts: 0.0.5(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(@rsbuild/core@1.0.1-rc.4)(typescript@5.0.2) optionalDependencies: '@microsoft/api-extractor': 7.43.1(@types/node@20.12.7) typescript: 5.0.2 - '@rspack/binding-darwin-arm64@1.0.0-rc.0': - optional: true - '@rspack/binding-darwin-arm64@1.0.3': optional: true - '@rspack/binding-darwin-x64@1.0.0-rc.0': - optional: true - '@rspack/binding-darwin-x64@1.0.3': optional: true - '@rspack/binding-linux-arm64-gnu@1.0.0-rc.0': - optional: true - '@rspack/binding-linux-arm64-gnu@1.0.3': optional: true - '@rspack/binding-linux-arm64-musl@1.0.0-rc.0': - optional: true - '@rspack/binding-linux-arm64-musl@1.0.3': optional: true - '@rspack/binding-linux-x64-gnu@1.0.0-rc.0': - optional: true - '@rspack/binding-linux-x64-gnu@1.0.3': optional: true - '@rspack/binding-linux-x64-musl@1.0.0-rc.0': - optional: true - '@rspack/binding-linux-x64-musl@1.0.3': optional: true - '@rspack/binding-win32-arm64-msvc@1.0.0-rc.0': - optional: true - '@rspack/binding-win32-arm64-msvc@1.0.3': optional: true - '@rspack/binding-win32-ia32-msvc@1.0.0-rc.0': - optional: true - '@rspack/binding-win32-ia32-msvc@1.0.3': optional: true - '@rspack/binding-win32-x64-msvc@1.0.0-rc.0': - optional: true - '@rspack/binding-win32-x64-msvc@1.0.3': optional: true - '@rspack/binding@1.0.0-rc.0': - optionalDependencies: - '@rspack/binding-darwin-arm64': 1.0.0-rc.0 - '@rspack/binding-darwin-x64': 1.0.0-rc.0 - '@rspack/binding-linux-arm64-gnu': 1.0.0-rc.0 - '@rspack/binding-linux-arm64-musl': 1.0.0-rc.0 - '@rspack/binding-linux-x64-gnu': 1.0.0-rc.0 - '@rspack/binding-linux-x64-musl': 1.0.0-rc.0 - '@rspack/binding-win32-arm64-msvc': 1.0.0-rc.0 - '@rspack/binding-win32-ia32-msvc': 1.0.0-rc.0 - '@rspack/binding-win32-x64-msvc': 1.0.0-rc.0 - '@rspack/binding@1.0.3': optionalDependencies: '@rspack/binding-darwin-arm64': 1.0.3 @@ -12087,25 +12002,16 @@ snapshots: '@rspack/binding-win32-arm64-msvc': 1.0.3 '@rspack/binding-win32-ia32-msvc': 1.0.3 '@rspack/binding-win32-x64-msvc': 1.0.3 - optional: true - '@rspack/core@1.0.0-rc.0(@swc/helpers@0.5.12)': + '@rspack/core@1.0.3(@swc/helpers@0.5.12)': dependencies: '@module-federation/runtime-tools': 0.5.1 - '@rspack/binding': 1.0.0-rc.0 + '@rspack/binding': 1.0.3 '@rspack/lite-tapable': 1.0.0 - caniuse-lite: 1.0.30001654 + caniuse-lite: 1.0.30001660 optionalDependencies: '@swc/helpers': 0.5.12 - '@rspack/core@1.0.3': - dependencies: - '@module-federation/runtime-tools': 0.5.1 - '@rspack/binding': 1.0.3 - '@rspack/lite-tapable': 1.0.0 - caniuse-lite: 1.0.30001654 - optional: true - '@rspack/lite-tapable@1.0.0': {} '@rspack/plugin-react-refresh@1.0.0(react-refresh@0.14.0)': @@ -12295,23 +12201,6 @@ snapshots: '@swc/core-win32-x64-msvc@1.4.0': optional: true - '@swc/core@1.4.0': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.6 - optionalDependencies: - '@swc/core-darwin-arm64': 1.4.0 - '@swc/core-darwin-x64': 1.4.0 - '@swc/core-linux-arm-gnueabihf': 1.4.0 - '@swc/core-linux-arm64-gnu': 1.4.0 - '@swc/core-linux-arm64-musl': 1.4.0 - '@swc/core-linux-x64-gnu': 1.4.0 - '@swc/core-linux-x64-musl': 1.4.0 - '@swc/core-win32-arm64-msvc': 1.4.0 - '@swc/core-win32-ia32-msvc': 1.4.0 - '@swc/core-win32-x64-msvc': 1.4.0 - optional: true - '@swc/core@1.4.0(@swc/helpers@0.5.1)': dependencies: '@swc/counter': 0.1.3 @@ -14347,7 +14236,7 @@ snapshots: caniuse-lite@1.0.30001616: {} - caniuse-lite@1.0.30001654: {} + caniuse-lite@1.0.30001660: {} center-align@0.1.3: dependencies: @@ -14829,7 +14718,7 @@ snapshots: create-require@1.1.1: {} - create-rstack@1.0.1: {} + create-rstack@1.0.2: {} cross-env@7.0.3: dependencies: @@ -14888,7 +14777,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.0 optionalDependencies: - '@rspack/core': 1.0.3 + '@rspack/core': 1.0.3(@swc/helpers@0.5.12) webpack: 5.94.0(webpack-cli@5.1.4) css-loader@6.11.0(@rspack/core@packages+rspack)(webpack@5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.8))(webpack-cli@5.1.4(webpack@5.94.0))): @@ -16771,7 +16660,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.12.7 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.0.2) + ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.0.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -17377,6 +17266,10 @@ snapshots: lru-cache@7.18.3: {} + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.9: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -17966,6 +17859,8 @@ snapshots: picocolors@1.0.1: {} + picocolors@1.1.0: {} + picomatch@2.3.1: {} pidtree@0.6.0: {} @@ -18029,7 +17924,7 @@ snapshots: yaml: 2.4.1 optionalDependencies: postcss: 8.4.38 - ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.0.2) + ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.0.2) postcss-loader@7.3.4(postcss@8.4.38)(typescript@4.9.5)(webpack@5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.1))(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -18795,11 +18690,12 @@ snapshots: rrweb-cssom@0.6.0: {} - rsbuild-plugin-dts@0.0.3(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(@rsbuild/core@1.0.1-beta.16)(typescript@5.0.2): + rsbuild-plugin-dts@0.0.5(@microsoft/api-extractor@7.43.1(@types/node@20.12.7))(@rsbuild/core@1.0.1-rc.4)(typescript@5.0.2): dependencies: - '@rsbuild/core': 1.0.1-beta.16 + '@rsbuild/core': 1.0.1-rc.4 fast-glob: 3.3.2 - picocolors: 1.0.1 + magic-string: 0.30.11 + picocolors: 1.1.0 optionalDependencies: '@microsoft/api-extractor': 7.43.1(@types/node@20.12.7) typescript: 5.0.2 @@ -19651,7 +19547,7 @@ snapshots: terser: 5.27.2 webpack: 5.92.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0)) optionalDependencies: - '@swc/core': 1.4.0 + '@swc/core': 1.4.0(@swc/helpers@0.5.1) terser-webpack-plugin@5.3.10(@swc/core@1.4.0)(webpack@5.94.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -19662,7 +19558,7 @@ snapshots: terser: 5.27.2 webpack: 5.94.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0)) optionalDependencies: - '@swc/core': 1.4.0 + '@swc/core': 1.4.0(@swc/helpers@0.5.1) terser-webpack-plugin@5.3.10(webpack@5.94.0(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -19851,7 +19747,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.4.0 + '@swc/core': 1.4.0(@swc/helpers@0.5.1) ts-node@10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.4.2): dependencies: @@ -19871,26 +19767,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.4.0 - - ts-node@10.9.2(@types/node@20.12.7)(typescript@5.0.2): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.12.7 - acorn: 8.11.3 - acorn-walk: 8.3.2 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.0.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - optional: true + '@swc/core': 1.4.0(@swc/helpers@0.5.1) tsc-alias@1.8.8: dependencies: From c67d4c2839228276216cbc15de5fbf4ee6dff5ec Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Wed, 11 Sep 2024 15:49:26 +0800 Subject: [PATCH 24/51] test(dev-server): enable dev server test cases (#7861) --- packages/rspack-dev-server/jest.config.js | 27 +++++++++++------------ packages/rspack-dev-server/package.json | 3 ++- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index 9999c192490..acb4b7b6a8b 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -8,21 +8,20 @@ const config = { }, testMatch: [ "/tests/*.test.ts", - ...(isWin || process.env.CI ? [] : ["/tests/e2e/*.test.js"]) + ...(isWin ? [] : ["/tests/e2e/*.test.js"]) ], - testPathIgnorePatterns: - isWin || process.env.CI - ? [] - : [ - // TODO: check why http proxy server throw error with websocket server - "/tests/e2e/allowed-hosts.test.js", - // TODO: check why this test timeout - "/tests/e2e/host.test.js", - // TODO: not support progress plugin event yet - "/tests/e2e/progress.test.js", - // TODO: check why this test throw error when run with other tests - "/tests/e2e/watch-files.test.js" - ], + testPathIgnorePatterns: isWin + ? [] + : [ + // TODO: check why http proxy server throw error with websocket server + "/tests/e2e/allowed-hosts.test.js", + // TODO: check why this test timeout + "/tests/e2e/host.test.js", + // TODO: not support progress plugin event yet + "/tests/e2e/progress.test.js", + // TODO: check why this test throw error when run with other tests + "/tests/e2e/watch-files.test.js" + ], cache: false, testTimeout: process.env.CI ? 120000 : 30000, transform: { diff --git a/packages/rspack-dev-server/package.json b/packages/rspack-dev-server/package.json index 3060392f170..db4c38aa4ff 100644 --- a/packages/rspack-dev-server/package.json +++ b/packages/rspack-dev-server/package.json @@ -20,7 +20,8 @@ "scripts": { "build": "tsc -b ./tsconfig.build.json", "dev": "tsc -w -b ./tsconfig.build.json", - "test": "rimraf .test-temp && cross-env NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --colors", + "test:install": "cross-env ./node_modules/.bin/puppeteer browsers install chrome", + "test": "pnpm run test:install && cross-env NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --colors", "api-extractor": "api-extractor run --verbose", "api-extractor:ci": "api-extractor run --verbose || diff temp/api.md etc/api.md" }, From d64eb75c3eb5dc1299ae2e1e8f81cc4fc0f42d8c Mon Sep 17 00:00:00 2001 From: 9aoy <9aoyuao@gmail.com> Date: Wed, 11 Sep 2024 16:48:55 +0800 Subject: [PATCH 25/51] docs: remove disableTransformByDefault (#7863) --- website/docs/en/config/module.mdx | 1 - website/docs/en/guide/tech/react.mdx | 5 ----- website/docs/zh/config/module.mdx | 3 --- website/docs/zh/guide/tech/react.mdx | 5 ----- 4 files changed, 14 deletions(-) diff --git a/website/docs/en/config/module.mdx b/website/docs/en/config/module.mdx index 446fada1930..7661439859c 100644 --- a/website/docs/en/config/module.mdx +++ b/website/docs/en/config/module.mdx @@ -979,7 +979,6 @@ Used to mark the type of the matching module, which affects how the module is ha - `'javascript/auto'`: JavaScript modules, supported module systems: CommonJS, ESM, no plans for AMD module support at this time. - `'javascript/esm'`:JavaScript modules, treated as ES Module. - `'javascript/dynamic'`:JavaScript modules, treated as Script. -- `'typescript'`: TypeScript module (🚧 deprecated, checkout [experiments.rspackFuture.disableTransformByDefault](/config/experiments#experimentsrspackfuturedisabletransformbydefault) for more details) - `'css'`: CSS module - `'css/module'`: CSS Modules module - `'css/auto'`: CSS Modules module if filename matches `/\.module(s)?\.[^.]+$/`, otherwise CSS module diff --git a/website/docs/en/guide/tech/react.mdx b/website/docs/en/guide/tech/react.mdx index 1a4422f1072..13e85782056 100644 --- a/website/docs/en/guide/tech/react.mdx +++ b/website/docs/en/guide/tech/react.mdx @@ -75,11 +75,6 @@ const ReactRefreshPlugin = require('@rspack/plugin-react-refresh'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { - experiments: { - rspackFuture: { - disableTransformByDefault: true, - }, - }, // ... mode: isDev ? 'development' : 'production', module: { diff --git a/website/docs/zh/config/module.mdx b/website/docs/zh/config/module.mdx index d0152e389da..712310c162b 100644 --- a/website/docs/zh/config/module.mdx +++ b/website/docs/zh/config/module.mdx @@ -978,9 +978,6 @@ module.exports = { - `'javascript/auto'`:JavaScript 模块,支持的模块系统:CommonJS、ESM,暂没有对 AMD 模块支持的计划。 - `'javascript/esm'`:JavaScript 模块,当作严格 ES Module 处理。 - `'javascript/dynamic'`:JavaScript 模块,当作 Script 处理。 -- `'jsx'`: 支持 JSX 的 JavaScript 模块(🚧 已废弃,详见 [experiments.rspackFuture.disableTransformByDefault](/config/experiments#experimentsrspackfuturedisabletransformbydefault))。 -- `'typescript'`:TypeScript 模块(🚧 已废弃)。 -- `'tsx'`: 支持 TSX 的 TypeScript 模块(🚧 已废弃)。 - `'css'`:CSS 模块。 - `'css/module'`:CSS Modules 模块。 - `'css/auto'`:基于文件名判断,若匹配`/\.module(s)?\.[^.]+$/`则为 CSS Modules 模块,否则为 CSS 模块。 diff --git a/website/docs/zh/guide/tech/react.mdx b/website/docs/zh/guide/tech/react.mdx index e1d67747246..139b7ccd60f 100644 --- a/website/docs/zh/guide/tech/react.mdx +++ b/website/docs/zh/guide/tech/react.mdx @@ -73,11 +73,6 @@ const ReactRefreshPlugin = require('@rspack/plugin-react-refresh'); const isDev = process.env.NODE_ENV === 'development'; module.exports = { - experiments: { - rspackFuture: { - disableTransformByDefault: true, - }, - }, // ... mode: isDev ? 'development' : 'production', module: { From b89fb12b869ad308003e8e27178b9e83411417b7 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Wed, 11 Sep 2024 18:12:35 +0800 Subject: [PATCH 26/51] test(dev-sever): add e2e test cases part 2 (#7829) --- packages/rspack-dev-server/client/index.js | 65 +- packages/rspack-dev-server/etc/api.md | 2 +- packages/rspack-dev-server/jest.config.js | 20 +- packages/rspack-dev-server/package.json | 8 +- packages/rspack-dev-server/src/server.ts | 71 +- .../__snapshots__/api.test.js.snap.webpack5 | 18 + .../__snapshots__/host.test.js.snap.webpack5 | 61 + .../hot-and-live-reload.test.js.snap.webpack5 | 385 +++ .../__snapshots__/ipc.test.js.snap.webpack5 | 41 + .../logging.test.js.snap.webpack5 | 333 ++ .../mime-types.test.js.snap.webpack5 | 17 + .../module-federation.test.js.snap.webpack5 | 25 + .../multi-compiler.test.js.snap.webpack5 | 265 ++ .../on-listening.test.js.snap.webpack5 | 21 + .../overlay.test.js.snap.webpack5 | 2884 ++++++++++++++++ .../tests/e2e/allowed-hosts.test.js | 2926 ++++++++--------- .../rspack-dev-server/tests/e2e/api.test.js | 1326 ++++---- .../rspack-dev-server/tests/e2e/host.test.js | 284 ++ .../tests/e2e/hot-and-live-reload.test.js | 898 +++++ .../rspack-dev-server/tests/e2e/ipc.test.js | 354 ++ .../tests/e2e/lazy-compilation.test.js | 112 + .../tests/e2e/logging.test.js | 242 ++ .../tests/e2e/mime-types.test.js | 135 + .../tests/e2e/module-federation.test.js | 296 ++ .../tests/e2e/multi-compiler.test.js | 778 +++++ .../tests/e2e/on-listening.test.js | 127 + .../tests/e2e/overlay.test.js | 1991 +++++++++++ .../fixtures/mime-types-config/file.custom | 1 + .../tests/fixtures/mime-types-config/foo.js | 5 + .../mime-types-config/webpack.config.js | 30 + .../module-federation-config/entry1.js | 3 + .../module-federation-config/entry2.js | 3 + .../webpack.config.js | 16 + .../webpack.multi.config.js | 18 + .../webpack.object-entry.config.js | 19 + .../webpack.plugin.js | 25 + .../foo.js | 3 + .../webpack.config.js | 24 + .../multi-compiler-two-configurations/one.js | 4 + .../multi-compiler-two-configurations/two.js | 3 + .../webpack.config.js | 44 + .../tests/fixtures/overlay-config/foo.js | 0 .../trusted-types.webpack.config.js | 21 + .../fixtures/overlay-config/webpack.config.js | 20 + .../tests/fixtures/reload-config/main.css | 1 - .../fixtures/reload-config/webpack.config.js | 15 +- .../universal-compiler-config/browser.js | 3 + .../universal-compiler-config/server.js | 3 + .../webpack.config.js | 43 + .../tests/helpers/html-generator-plugin.js | 2 +- .../tests/helpers/normalize.js | 49 + .../trusted-types-html-generator-plugin.js | 82 + pnpm-lock.yaml | 23 + 53 files changed, 11955 insertions(+), 2190 deletions(-) create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/host.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/hot-and-live-reload.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/ipc.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/logging.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/mime-types.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/module-federation.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/on-listening.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/host.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/hot-and-live-reload.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/ipc.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/lazy-compilation.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/logging.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/mime-types.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/module-federation.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/multi-compiler.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/on-listening.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/overlay.test.js create mode 100644 packages/rspack-dev-server/tests/fixtures/mime-types-config/file.custom create mode 100644 packages/rspack-dev-server/tests/fixtures/mime-types-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/mime-types-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/entry1.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/entry2.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.multi.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.object-entry.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.plugin.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/one.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/two.js create mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/overlay-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/overlay-config/trusted-types.webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/overlay-config/webpack.config.js delete mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/main.css create mode 100644 packages/rspack-dev-server/tests/fixtures/universal-compiler-config/browser.js create mode 100644 packages/rspack-dev-server/tests/fixtures/universal-compiler-config/server.js create mode 100644 packages/rspack-dev-server/tests/fixtures/universal-compiler-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/helpers/normalize.js create mode 100644 packages/rspack-dev-server/tests/helpers/trusted-types-html-generator-plugin.js diff --git a/packages/rspack-dev-server/client/index.js b/packages/rspack-dev-server/client/index.js index c927cc80a85..87fa389986c 100644 --- a/packages/rspack-dev-server/client/index.js +++ b/packages/rspack-dev-server/client/index.js @@ -106,6 +106,28 @@ var status = { currentHash: typeof __webpack_hash__ !== "undefined" ? __webpack_hash__ : "" }; +var decodeOverlayOptions = function decodeOverlayOptions(overlayOptions) { + if (typeof overlayOptions === "object") { + ["warnings", "errors", "runtimeErrors"].forEach(function (property) { + if (typeof overlayOptions[property] === "string") { + var overlayFilterFunctionString = decodeURIComponent( + overlayOptions[property] + ); + + // eslint-disable-next-line no-new-func + var overlayFilterFunction = new Function( + "message", + "var callback = ".concat( + overlayFilterFunctionString, + "\n return callback(message)" + ) + ); + overlayOptions[property] = overlayFilterFunction; + } + }); + } +}; + /** @type {Options} */ var options = { hot: false, @@ -132,6 +154,7 @@ if (parsedResourceQuery.progress === "true") { options.progress = true; enabledFeatures.Progress = true; } + if (parsedResourceQuery.overlay) { try { options.overlay = JSON.parse(parsedResourceQuery.overlay); @@ -149,6 +172,7 @@ if (parsedResourceQuery.overlay) { }, options.overlay ); + decodeOverlayOptions(options.overlay); } enabledFeatures.Overlay = true; } @@ -232,6 +256,7 @@ var onSocketMessage = { return; } options.overlay = value; + decodeOverlayOptions(options.overlay); }, /** * @param {number} value @@ -321,16 +346,22 @@ var onSocketMessage = { for (var i = 0; i < printableWarnings.length; i++) { log.warn(printableWarnings[i]); } - var needShowOverlayForWarnings = + var overlayWarningsSetting = typeof options.overlay === "boolean" ? options.overlay : options.overlay && options.overlay.warnings; - if (needShowOverlayForWarnings) { - overlay.send({ - type: "BUILD_ERROR", - level: "warning", - messages: _warnings - }); + if (overlayWarningsSetting) { + var warningsToDisplay = + typeof overlayWarningsSetting === "function" + ? _warnings.filter(overlayWarningsSetting) + : _warnings; + if (warningsToDisplay.length) { + overlay.send({ + type: "BUILD_ERROR", + level: "warning", + messages: _warnings + }); + } } if (params && params.preventReloading) { return; @@ -352,16 +383,22 @@ var onSocketMessage = { for (var i = 0; i < printableErrors.length; i++) { log.error(printableErrors[i]); } - var needShowOverlayForErrors = + var overlayErrorsSettings = typeof options.overlay === "boolean" ? options.overlay : options.overlay && options.overlay.errors; - if (needShowOverlayForErrors) { - overlay.send({ - type: "BUILD_ERROR", - level: "error", - messages: _errors - }); + if (overlayErrorsSettings) { + var errorsToDisplay = + typeof overlayErrorsSettings === "function" + ? _errors.filter(overlayErrorsSettings) + : _errors; + if (errorsToDisplay.length) { + overlay.send({ + type: "BUILD_ERROR", + level: "error", + messages: _errors + }); + } } }, /** diff --git a/packages/rspack-dev-server/etc/api.md b/packages/rspack-dev-server/etc/api.md index a85de18a4ec..1e29baba633 100644 --- a/packages/rspack-dev-server/etc/api.md +++ b/packages/rspack-dev-server/etc/api.md @@ -63,7 +63,7 @@ export class RspackDevServer extends WebpackDevServer { constructor(options: Configuration, compiler: Compiler | MultiCompiler); compiler: Compiler | MultiCompiler; // (undocumented) - static getFreePort(port: string, host: string): Promise; + static getFreePort: (port: string, host: string) => Promise; // (undocumented) initialize(): Promise; options: ResolvedDevServer; diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index acb4b7b6a8b..5207445e398 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -13,15 +13,15 @@ const config = { testPathIgnorePatterns: isWin ? [] : [ - // TODO: check why http proxy server throw error with websocket server - "/tests/e2e/allowed-hosts.test.js", - // TODO: check why this test timeout - "/tests/e2e/host.test.js", - // TODO: not support progress plugin event yet - "/tests/e2e/progress.test.js", - // TODO: check why this test throw error when run with other tests - "/tests/e2e/watch-files.test.js" - ], + // TODO: check why http proxy server throw error with websocket server + "/tests/e2e/allowed-hosts.test.js", + // TODO: check why this test timeout + "/tests/e2e/host.test.js", + // TODO: not support progress plugin event yet + "/tests/e2e/progress.test.js", + // TODO: check why this test throw error when run with other tests + "/tests/e2e/watch-files.test.js" + ], cache: false, testTimeout: process.env.CI ? 120000 : 30000, transform: { @@ -33,7 +33,7 @@ const config = { ] }, // Add this to find out which test timeouts - testSequencer: "/tests/helpers/sequencer.js", + // testSequencer: "/tests/helpers/sequencer.js", snapshotResolver: "/tests/helpers/snapshot-resolver.js", setupFilesAfterEnv: ["/tests/helpers/setup-test.js"], globalSetup: "/tests/helpers/global-setup-test.js", diff --git a/packages/rspack-dev-server/package.json b/packages/rspack-dev-server/package.json index db4c38aa4ff..7bde2da2873 100644 --- a/packages/rspack-dev-server/package.json +++ b/packages/rspack-dev-server/package.json @@ -51,7 +51,13 @@ "sockjs-client": "^1.6.1", "supertest": "^6.1.3", "tcp-port-used": "^1.0.2", - "typescript": "5.0.2" + "typescript": "5.0.2", + "style-loader": "^3.3.3", + "css-loader": "^6.11.0", + "require-from-string": "^2.0.2", + "wait-for-expect": "^3.0.2", + "prettier": "3.2.5", + "@jest/test-sequencer": "^29.7.0" }, "dependencies": { "chokidar": "^3.6.0", diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 56664f4ccf6..000e95b80d8 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -14,7 +14,9 @@ import type { Socket } from "node:net"; import { type Compiler, MultiCompiler } from "@rspack/core"; import type { FSWatcher } from "chokidar"; import rdm from "webpack-dev-middleware"; -import WebpackDevServer from "webpack-dev-server"; +import WebpackDevServer, { + type OverlayMessageOptions +} from "webpack-dev-server"; // @ts-ignore 'package.json' is not under 'rootDir' import { version } from "../package.json"; @@ -23,31 +25,39 @@ import { applyDevServerPatch } from "./patch"; applyDevServerPatch(); -export class RspackDevServer extends WebpackDevServer { - static async getFreePort(port: string, host: string) { - if (typeof port !== "undefined" && port !== null && port !== "auto") { - return port; - } +const encodeOverlaySettings = (setting: OverlayMessageOptions | undefined) => + typeof setting === "function" + ? encodeURIComponent(setting.toString()) + : setting; - const pRetry = require("p-retry"); - const getPort = require("webpack-dev-server/lib/getPort"); - const basePort = - typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined" - ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10) - : 8080; - - // Try to find unused port and listen on it for 3 times, - // if port is not specified in options. - const defaultPortRetry = - typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined" - ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) - : 3; - - return pRetry(() => getPort(basePort, host), { - retries: defaultPortRetry - }); +const getFreePort = async function getFreePort(port: string, host: string) { + if (typeof port !== "undefined" && port !== null && port !== "auto") { + return port; } + const pRetry = require("p-retry"); + const getPort = require("webpack-dev-server/lib/getPort"); + const basePort = + typeof process.env.WEBPACK_DEV_SERVER_BASE_PORT !== "undefined" + ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_BASE_PORT, 10) + : 8080; + + // Try to find unused port and listen on it for 3 times, + // if port is not specified in options. + const defaultPortRetry = + typeof process.env.WEBPACK_DEV_SERVER_PORT_RETRY !== "undefined" + ? Number.parseInt(process.env.WEBPACK_DEV_SERVER_PORT_RETRY, 10) + : 3; + + return pRetry(() => getPort(basePort, host), { + retries: defaultPortRetry + }); +}; + +WebpackDevServer.getFreePort = getFreePort; + +export class RspackDevServer extends WebpackDevServer { + static getFreePort = getFreePort; /** * resolved after `normalizedOptions` */ @@ -385,12 +395,19 @@ export class RspackDevServer extends WebpackDevServer { } if (typeof client.overlay !== "undefined") { - searchParams.set( - "overlay", + const overlayString = typeof client.overlay === "boolean" ? String(client.overlay) - : JSON.stringify(client.overlay) - ); + : JSON.stringify({ + ...client.overlay, + errors: encodeOverlaySettings(client.overlay.errors), + warnings: encodeOverlaySettings(client.overlay.warnings), + runtimeErrors: encodeOverlaySettings( + client.overlay.runtimeErrors + ) + }); + + searchParams.set("overlay", overlayString); } if (typeof client.reconnect !== "undefined") { diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 index 17f0b95dff9..39890fbe5a9 100644 --- a/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 @@ -24,6 +24,24 @@ exports[`API Invalidate callback should use the provided \`callback\` function: exports[`API Invalidate callback should use the provided \`callback\` function: response status 1`] = `200`; +exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "WebSocket connection to 'ws://test.host:8158/ws' failed: Error in connection establishment: net::ERR_NAME_NOT_RESOLVED", + "[webpack-dev-server] JSHandle@object", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: page errors 1`] = `[]`; + +exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: response status 1`] = `200`; + +exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: web socket URL 1`] = `"ws://test.host:8158/ws"`; + exports[`API Server.getFreePort should retry finding the port for up to defaultPortRetry times (number): console messages 1`] = ` [ "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/host.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/host.test.js.snap.webpack5 new file mode 100644 index 00000000000..3aa742bc730 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/host.test.js.snap.webpack5 @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`host should work using "127.0.0.1" host and "auto" port: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "127.0.0.1" host and "auto" port: page errors 1`] = `[]`; + +exports[`host should work using "127.0.0.1" host and port as number: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "127.0.0.1" host and port as number: page errors 1`] = `[]`; + +exports[`host should work using "127.0.0.1" host and port as string: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "127.0.0.1" host and port as string: page errors 1`] = `[]`; + +exports[`host should work using "localhost" host and "auto" port: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "localhost" host and "auto" port: page errors 1`] = `[]`; + +exports[`host should work using "localhost" host and port as number: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "localhost" host and port as number: page errors 1`] = `[]`; + +exports[`host should work using "localhost" host and port as string: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`host should work using "localhost" host and port as string: page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/hot-and-live-reload.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/hot-and-live-reload.test.js.snap.webpack5 new file mode 100644 index 00000000000..3d7a793295a --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/hot-and-live-reload.test.js.snap.webpack5 @@ -0,0 +1,385 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should not refresh content when hot and no live reload disabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work and allow to disable hot module replacement and live reload using the "webpack-dev-server-hot=false&webpack-dev-server-live-reload=false" (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should work and allow to disable hot module replacement and live reload using the "webpack-dev-server-hot=false&webpack-dev-server-live-reload=false" (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and allow to disable hot module replacement using the "webpack-dev-server-hot=false" (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`hot and live reload should work and allow to disable hot module replacement using the "webpack-dev-server-hot=false" (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and allow to disable live reload using the "webpack-dev-server-live-reload=false" (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should work and allow to disable live reload using the "webpack-dev-server-live-reload=false" (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and do nothing when web socket server disabled (default): console messages 1`] = `[]`; + +exports[`hot and live reload should work and do nothing when web socket server disabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when hot enabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload and hot enabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload and hot enabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload and hot enabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload and hot enabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload disabled and hot enabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled and hot disabled (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work and refresh content using hot module replacement when live reload enabled and hot disabled (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using live reload (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", +] +`; + +exports[`hot and live reload should work and refresh content using live reload (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using live reload when live reload disabled and hot enabled (sockjs): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", +] +`; + +exports[`hot and live reload should work and refresh content using live reload when live reload disabled and hot enabled (sockjs): page errors 1`] = `[]`; + +exports[`hot and live reload should work and refresh content using live reload when live reload enabled and hot disabled (ws): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", +] +`; + +exports[`hot and live reload should work and refresh content using live reload when live reload enabled and hot disabled (ws): page errors 1`] = `[]`; + +exports[`hot and live reload should work with manual client setup (default): console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay disabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work with manual client setup (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work with manual client setup and allow to disable hot module replacement (default): console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay disabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay disabled.", +] +`; + +exports[`hot and live reload should work with manual client setup and allow to disable hot module replacement (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work with manual client setup and allow to disable live reload (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay disabled.", + "[webpack-dev-server] App updated. Recompiling...", +] +`; + +exports[`hot and live reload should work with manual client setup and allow to disable live reload (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work with manual client setup and allow to enable hot module replacement (default): console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay disabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Updated modules:", + "[HMR] - ./main.css", + "[HMR] - ../../../../../node_modules/.pnpm/css-loader@6.11.0_@rspack+core@packages+rspack_webpack@5.94.0_webpack-cli@5.1.4_webpack@5.94.0__/node_modules/css-loader/dist/cjs.js!./main.css", + "", + "[HMR] App is up to date.", +] +`; + +exports[`hot and live reload should work with manual client setup and allow to enable hot module replacement (default): page errors 1`] = `[]`; + +exports[`hot and live reload should work with manual client setup and allow to enable live reload (default): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay disabled.", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay disabled.", +] +`; + +exports[`hot and live reload should work with manual client setup and allow to enable live reload (default): page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/ipc.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/ipc.test.js.snap.webpack5 new file mode 100644 index 00000000000..b8508659b44 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/ipc.test.js.snap.webpack5 @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web socket server URL should work with the "ipc" option using "string" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "ipc" option using "string" value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "ipc" option using "string" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "ipc" option using "string" value ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "ipc" option using "true" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "ipc" option using "true" value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "ipc" option using "true" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "ipc" option using "true" value ("ws"): page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/logging.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/logging.test.js.snap.webpack5 new file mode 100644 index 00000000000..6258a968c9f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/logging.test.js.snap.webpack5 @@ -0,0 +1,333 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`logging should work and do not log messages about hot and live reloading is enabled (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled., + Hey., +] +`; + +exports[`logging should work and do not log messages about hot and live reloading is enabled (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled., + Hey., +] +`; + +exports[`logging should work and log errors by default (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log errors by default (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log message about live reloading is enabled (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled., + Hey., +] +`; + +exports[`logging should work and log message about live reloading is enabled (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (sockjs) 2`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (sockjs) 3`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (ws) 2`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot and live reloading is enabled (ws) 3`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot is enabled (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log messages about hot is enabled (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log only error (sockjs) 1`] = ` +[ + Hey., + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log only error (ws) 1`] = ` +[ + Hey., + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log static changes (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] "/tests/fixtures/client-config/static/foo.txt" from static directory was changed. Reloading..., + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log static changes (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] "/tests/fixtures/client-config/static/foo.txt" from static directory was changed. Reloading..., + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work and log warning and errors (sockjs) 1`] = ` +[ + Hey., + [webpack-dev-server] Warnings while compiling., + [webpack-dev-server] WARNING + ⚠ Error: Warning from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log warning and errors (ws) 1`] = ` +[ + Hey., + [webpack-dev-server] Warnings while compiling., + [webpack-dev-server] WARNING + ⚠ Error: Warning from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, + [webpack-dev-server] Errors while compiling. Reload prevented., + [webpack-dev-server] ERROR + × Error: Error from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log warnings by default (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] Warnings while compiling., + [webpack-dev-server] WARNING + ⚠ Error: Warning from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work and log warnings by default (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., + [webpack-dev-server] Warnings while compiling., + [webpack-dev-server] WARNING + ⚠ Error: Warning from compilation + │ at Object.fn (/tests/e2e/logging.test.js::) + │ at SyncHook.callAsyncStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at /packages/rspack/dist/Compiler.js:: + │ at last.function (/packages/rspack/dist/Compiler.js::) +, +] +`; + +exports[`logging should work when the "client.logging" is "info" (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "info" (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "log" (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "log" (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "none" (sockjs) 1`] = ` +[ + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "none" (ws) 1`] = ` +[ + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "verbose" (sockjs) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; + +exports[`logging should work when the "client.logging" is "verbose" (ws) 1`] = ` +[ + [webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled., + [HMR] Waiting for update signal from WDS..., + Hey., +] +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/mime-types.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/mime-types.test.js.snap.webpack5 new file mode 100644 index 00000000000..62552683489 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/mime-types.test.js.snap.webpack5 @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`mimeTypes option as an object with a custom type should request file with different js mime type: console messages 1`] = `[]`; + +exports[`mimeTypes option as an object with a custom type should request file with different js mime type: page errors 1`] = `[]`; + +exports[`mimeTypes option as an object with a custom type should request file with different js mime type: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`mimeTypes option as an object with a custom type should request file with different js mime type: response status 1`] = `200`; + +exports[`mimeTypes option as an object with a remapped type should request file with different js mime type: console messages 1`] = `[]`; + +exports[`mimeTypes option as an object with a remapped type should request file with different js mime type: page errors 1`] = `[]`; + +exports[`mimeTypes option as an object with a remapped type should request file with different js mime type: response headers content-type 1`] = `"text/plain; charset=utf-8"`; + +exports[`mimeTypes option as an object with a remapped type should request file with different js mime type: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/module-federation.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/module-federation.test.js.snap.webpack5 new file mode 100644 index 00000000000..8d4ba07250a --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/module-federation.test.js.snap.webpack5 @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Module federation should use plugin should contain hot script in main.js: console messages 1`] = `[]`; + +exports[`Module federation should use plugin should contain hot script in main.js: page errors 1`] = `[]`; + +exports[`Module federation should use plugin should contain hot script in remoteEntry.js: console messages 1`] = `[]`; + +exports[`Module federation should use plugin should contain hot script in remoteEntry.js: page errors 1`] = `[]`; + +exports[`Module federation should work with multi compiler config should use the last entry export: console messages 1`] = `[]`; + +exports[`Module federation should work with multi compiler config should use the last entry export: page errors 1`] = `[]`; + +exports[`Module federation should work with object multi-entry config should support the named entry export: console messages 1`] = `[]`; + +exports[`Module federation should work with object multi-entry config should support the named entry export: page errors 1`] = `[]`; + +exports[`Module federation should work with object multi-entry config should use the last entry export: console messages 1`] = `[]`; + +exports[`Module federation should work with object multi-entry config should use the last entry export: page errors 1`] = `[]`; + +exports[`Module federation should work with simple multi-entry config should use the last entry export: console messages 1`] = `[]`; + +exports[`Module federation should work with simple multi-entry config should use the last entry export: page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 new file mode 100644 index 00000000000..7a06874d381 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/multi-compiler.test.js.snap.webpack5 @@ -0,0 +1,265 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`multi compiler should work with one web target configuration and do nothing: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`multi compiler should work with one web target configuration and do nothing: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration and do nothing: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration and do nothing: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration when hot and live reloads are enabled, and do hot reload for browser compiler by default when browser entry changed: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./browser.js is not accepted +Update propagation: ./browser.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when hot and live reloads are enabled, and do hot reload for browser compiler by default when browser entry changed: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration when only hot reload is enabled, and do hot reload for browser compiler when browser entry changed: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./browser.js is not accepted +Update propagation: ./browser.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when only hot reload is enabled, and do hot reload for browser compiler when browser entry changed: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing browser and server entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing browser and server entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing browser and server entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing browser and server entries: page errors 2`] = `[]`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing server and browser entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing server and browser entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "Hello from the browser", +] +`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing server and browser entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing server and browser entries: page errors 2`] = `[]`; + +exports[`multi compiler should work with web target configurations and do nothing: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "one", +] +`; + +exports[`multi compiler should work with web target configurations and do nothing: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "two", +] +`; + +exports[`multi compiler should work with web target configurations and do nothing: page errors 1`] = `[]`; + +exports[`multi compiler should work with web target configurations and do nothing: page errors 2`] = `[]`; + +exports[`multi compiler should work with web target configurations when hot and live reloads are enabled, and do hot reload by default when changing own entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "one", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./one.js is not accepted +Update propagation: ./one.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "one", +] +`; + +exports[`multi compiler should work with web target configurations when hot and live reloads are enabled, and do hot reload by default when changing own entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "two", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./two.js is not accepted +Update propagation: ./two.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "two", +] +`; + +exports[`multi compiler should work with web target configurations when hot and live reloads are enabled, and do hot reload by default when changing own entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with web target configurations when hot and live reloads are enabled, and do hot reload by default when changing own entries: page errors 2`] = `[]`; + +exports[`multi compiler should work with web target configurations when only hot reload is enabled, and do hot reload when changing own entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "one", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./one.js is not accepted +Update propagation: ./one.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "one", +] +`; + +exports[`multi compiler should work with web target configurations when only hot reload is enabled, and do hot reload when changing own entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "two", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "[HMR] Cannot apply update. Need to do a full reload!", + "[HMR] Error: Aborted because ./two.js is not accepted +Update propagation: ./two.js + ", + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading disabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "two", +] +`; + +exports[`multi compiler should work with web target configurations when only hot reload is enabled, and do hot reload when changing own entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with web target configurations when only hot reload is enabled, and do hot reload when changing own entries: page errors 2`] = `[]`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled and do live reload when changing other entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "one", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "one", +] +`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled and do live reload when changing other entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "two", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "two", +] +`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled and do live reload when changing other entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled and do live reload when changing other entries: page errors 2`] = `[]`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled, and do live reload when changing own entries: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "one", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "one", +] +`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled, and do live reload when changing own entries: console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "two", + "[webpack-dev-server] App updated. Recompiling...", + "[webpack-dev-server] App updated. Reloading...", + "[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "two", +] +`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled, and do live reload when changing own entries: page errors 1`] = `[]`; + +exports[`multi compiler should work with web target configurations when only live reload is enabled, and do live reload when changing own entries: page errors 2`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/on-listening.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/on-listening.test.js.snap.webpack5 new file mode 100644 index 00000000000..3a34989809a --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/on-listening.test.js.snap.webpack5 @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`onListening option should handle GET request to /listening/some/path route: console messages 1`] = `[]`; + +exports[`onListening option should handle GET request to /listening/some/path route: page errors 1`] = `[]`; + +exports[`onListening option should handle GET request to /listening/some/path route: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`onListening option should handle GET request to /listening/some/path route: response status 1`] = `200`; + +exports[`onListening option should handle GET request to /listening/some/path route: response text 1`] = `"listening"`; + +exports[`onListening option should handle POST request to /listening/some/path route: console messages 1`] = `[]`; + +exports[`onListening option should handle POST request to /listening/some/path route: page errors 1`] = `[]`; + +exports[`onListening option should handle POST request to /listening/some/path route: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`onListening option should handle POST request to /listening/some/path route: response status 1`] = `200`; + +exports[`onListening option should handle POST request to /listening/some/path route: response text 1`] = `"listening POST"`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 new file mode 100644 index 00000000000..80adb4be5bf --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 @@ -0,0 +1,2884 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`overlay should not show a warning when "client.overlay" is "false": page html 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show a warning when "client.overlay.warnings" is "false": page html 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show an error when "client.overlay" is "false": page html 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show an error when "client.overlay.errors" is "false": page html 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error and allow to close: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR in ./foo.js +
+
+ × Module parse failed: ╰─▶ × JavaScript parsing error: Unexpected eof + ╭──── 1 │ \`; ╰──── help: You may need an appropriate loader to handle + this file type. +
+
+
+
+ + +`; + +exports[`overlay should not show initially, then show on an error and allow to close: page html after close 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error and allow to close: page html initial 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error and allow to close: page html with error 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should not show initially, then show on an error, then hide on fix: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR in ./foo.js +
+
+ × Module parse failed: ╰─▶ × JavaScript parsing error: Unexpected eof + ╭──── 1 │ \`; ╰──── help: You may need an appropriate loader to handle + this file type. +
+
+
+
+ + +`; + +exports[`overlay should not show initially, then show on an error, then hide on fix: page html after fix error 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error, then hide on fix: page html initial 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error, then hide on fix: page html with error 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR in ./foo.js +
+
+ × Module parse failed: ╰─▶ × JavaScript parsing error: Unexpected eof + ╭──── 1 │ \`; ╰──── help: You may need an appropriate loader to handle + this file type. +
+
+
+
+ + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: overlay html 2`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR in ./foo.js +
+
+ × Module parse failed: ╰─▶ × JavaScript parsing error: Unexpected eof + ╭──── 1 │ \`;a ╰──── help: You may need an appropriate loader to handle + this file type. +
+
+
+
+ + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: page html after fix error 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: page html initial 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: page html with error 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should not show initially, then show on an error, then show other error, then hide on fix: page html with other error 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning after invalidation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning after invalidation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning and error for initial compilation and protects against xss: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ ⚠ Error: <strong>strong</strong> │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ ERROR +
+
+ × Error: <strong>strong</strong> │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning and error for initial compilation and protects against xss: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning and error for initial compilation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ ERROR +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning and error for initial compilation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning and hide them after closing connection: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning and hide them after closing connection: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning and hide them after closing connection: page html 2`] = ` + +

webpack-dev-server is running...

+ + + +`; + +exports[`overlay should show a warning for initial compilation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning for initial compilation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning when "client.overlay" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning when "client.overlay" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning when "client.overlay.errors" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning when "client.overlay.errors" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show a warning when "client.overlay.warnings" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show a warning when "client.overlay.warnings" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an ansi formatted error for initial compilation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: + + 18 | + Render + ansi formatted text + │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) + +
+
+
+
+ + +`; + +exports[`overlay should show an ansi formatted error for initial compilation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an error after invalidation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show an error after invalidation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an error for initial compilation: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show an error for initial compilation: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an error when "client.overlay" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show an error when "client.overlay" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an error when "client.overlay.errors" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show an error when "client.overlay.errors" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show an error when "client.overlay.warnings" is "true": overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Warning from compilation │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show an error when "client.overlay.warnings" is "true": page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show error for uncaught promise rejection: overlay html 1`] = ` + +
+
+ Uncaught runtime errors: +
+ +
+
+
+ ERROR +
+
+ Async error at <anonymous>:: +
+
+
+
+ + +`; + +exports[`overlay should show error for uncaught runtime error: overlay html 1`] = ` + +
+
+ Uncaught runtime errors: +
+ +
+
+
+ ERROR +
+
+ Injected error at throwError (<anonymous>::) at + <anonymous>:: +
+
+
+
+ + +`; + +exports[`overlay should show error when it is not filtered: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Unfiltered error │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show error when it is not filtered: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show overlay when "Content-Security-Policy" is "default-src 'self'" was used: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show overlay when "Content-Security-Policy" is "default-src 'self'" was used: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show warning when it is not filtered: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ WARNING +
+
+ ⚠ Error: Unfiltered warning │ at Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show warning when it is not filtered: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; diff --git a/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js b/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js index e233ff9c22f..e31c2b54cdc 100644 --- a/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js +++ b/packages/rspack-dev-server/tests/e2e/allowed-hosts.test.js @@ -11,1521 +11,1521 @@ const [port1, port2] = require("../helpers/ports-map")["allowed-hosts"]; const webSocketServers = ["ws", "sockjs"]; describe("allowed hosts", () => { - for (const webSocketServer of webSocketServers) { - it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("host", "my-test-host"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2, - protocol: "ws" - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto", - server: "https" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("host", "my-test-host"); - }, - target: `https://${devServerHost}:${devServerPort}`, - secure: false, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - const devServerHost = "localhost"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - // it(`should connect web socket client using "[::1] host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - // const devServerHost = "::1"; - // const devServerPort = port1; - // const proxyHost = devServerHost; - // const proxyPort = port2; - - // const compiler = webpack(config); - // const devServerOptions = { - // client: { - // webSocketURL: { - // port: port2 - // } - // }, - // webSocketServer, - // port: devServerPort, - // host: devServerHost, - // allowedHosts: "auto" - // }; - // const server = new Server(devServerOptions, compiler); - - // await server.start(); - - // function startProxy(callback) { - // const app = express(); - - // app.use( - // "/", - // createProxyMiddleware({ - // target: `http://[${devServerHost}]:${devServerPort}`, - // ws: true, - // changeOrigin: true, - // logLevel: "warn" - // }) - // ); - - // return app.listen(proxyPort, proxyHost, callback); - // } - - // const proxy = await new Promise(resolve => { - // const proxyCreated = startProxy(() => { - // resolve(proxyCreated); - // }); - // }); - - // const { page, browser } = await runBrowser(); - - // try { - // const pageErrors = []; - // const consoleMessages = []; - - // page - // .on("console", message => { - // consoleMessages.push(message); - // }) - // .on("pageerror", error => { - // pageErrors.push(error); - // }); - - // await page.goto(`http://[${proxyHost}]:${proxyPort}/`, { - // waitUntil: "networkidle0" - // }); - - // expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - // "console messages" - // ); - // expect(pageErrors).toMatchSnapshot("page errors"); - // } catch (error) { - // throw error; - // } finally { - // proxy.close(); - - // await browser.close(); - // await server.stop(); - // } - // }); - - it(`should connect web socket client using "file:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - target: `http://${devServerHost}:${devServerPort}`, - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "file:///path/to/local/file.js"); - }, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - target: `http://${devServerHost}:${devServerPort}`, - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "chrome-extension:///abcdef"); - }, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom hostname to web socket server with the "all" value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "all" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom hostname to web socket server with the "all" value in array ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: ["all"] - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "my-test-origin.com" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: ".my-test-origin.com" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: ".my-test-origin.com" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader( - "origin", - "http://foo.bar.baz.my-test-origin.com/" - ); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: ["my-test-origin.com"] - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReqWs: proxyReq => { - proxyReq.setHeader("origin", "http://my-test-origin.com/"); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - - it(`should disconnect web client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { - const devServerHost = "127.0.0.1"; - const devServerPort = port1; - const proxyHost = devServerHost; - const proxyPort = port2; - - const compiler = webpack(config); - const devServerOptions = { - client: { - webSocketURL: { - port: port2 - } - }, - webSocketServer, - port: devServerPort, - host: devServerHost, - allowedHosts: "auto" - }; - const server = new Server(devServerOptions, compiler); - - await server.start(); - - function startProxy(callback) { - const app = express(); - - app.use( - "/", - createProxyMiddleware({ - // Emulation - onProxyReq: (proxyReq, req, res) => { - proxyReq.setHeader("host", "unknown"); - res.setHeader("host", devServerHost); - }, - target: `http://${devServerHost}:${devServerPort}`, - ws: true, - changeOrigin: true, - logLevel: "warn" - }) - ); - - return app.listen(proxyPort, proxyHost, callback); - } - - const proxy = await new Promise(resolve => { - const proxyCreated = startProxy(() => { - resolve(proxyCreated); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://${proxyHost}:${proxyPort}/`, { - waitUntil: "networkidle0" - }); - - const html = await page.content(); - - expect(html).toMatchSnapshot("html"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - proxy.close(); - - await browser.close(); - await server.stop(); - } - }); - } - - describe("check host headers", () => { - let compiler; - let server; - let page; - let browser; - let pageErrors; - let consoleMessages; - - beforeEach(() => { - compiler = webpack(config); - pageErrors = []; - consoleMessages = []; - }); - - afterEach(async () => { - await browser.close(); - await server.stop(); - }); - - it("should always allow `localhost` if options.allowedHosts is auto", async () => { - const options = { - allowedHosts: "auto", - port: port1 - }; - - const headers = { - host: "localhost" - }; - - server = new Server(options, compiler); - - await server.start(); - - ({ page, browser } = await runBrowser()); - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); - - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); - - it("should always allow `localhost` subdomain if options.allowedHosts is auto", async () => { - const options = { - allowedHosts: "auto", - port: port1 - }; - - const headers = { - host: "app.localhost" - }; - - server = new Server(options, compiler); - - await server.start(); - - ({ page, browser } = await runBrowser()); - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); - - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); + for (const webSocketServer of webSocketServers) { + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("host", "my-test-host"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "host" header when "server: 'https'" is enabled ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2, + protocol: "ws" + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto", + server: "https" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("host", "my-test-host"); + }, + target: `https://${devServerHost}:${devServerPort}`, + secure: false, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web socket client using custom hostname from web socket server with the "auto" value based on the "origin" header ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "localhost"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "127.0.0.1" host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "[::1] host to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "::1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://[${devServerHost}]:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://[${proxyHost}]:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "file:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "file:///path/to/local/file.js"); + }, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using "chrome-extension:" protocol to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "chrome-extension:///abcdef"); + }, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the "all" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the "all" value in array ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ["all"] + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the custom hostname value starting with dot ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ".my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom sub hostname to web socket server with the custom hostname value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ".my-test-origin.com" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader( + "origin", + "http://foo.bar.baz.my-test-origin.com/" + ); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should connect web socket client using custom hostname to web socket server with the multiple custom hostname values ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: ["my-test-origin.com"] + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReqWs: proxyReq => { + proxyReq.setHeader("origin", "http://my-test-origin.com/"); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should disconnect web client using localhost to web socket server with the "auto" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port2 + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "auto" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + // Emulation + onProxyReq: (proxyReq, req, res) => { + proxyReq.setHeader("host", "unknown"); + res.setHeader("host", devServerHost); + }, + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const html = await page.content(); + + expect(html).toMatchSnapshot("html"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + } + + describe("check host headers", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(() => { + compiler = webpack(config); + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should always allow `localhost` if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1 + }; + + const headers = { + host: "localhost" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should always allow `localhost` subdomain if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1 + }; + + const headers = { + host: "app.localhost" + }; + + server = new Server(options, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); - // it("should always allow value from the `host` options if options.allowedHosts is auto", async () => { - // const networkIP = Server.internalIPSync("v4"); - // const options = { - // host: networkIP, - // allowedHosts: "auto", - // port: port1 - // }; + it("should always allow value from the `host` options if options.allowedHosts is auto", async () => { + const networkIP = Server.internalIPSync("v4"); + const options = { + host: networkIP, + allowedHosts: "auto", + port: port1 + }; - // const headers = { - // host: networkIP - // }; + const headers = { + host: networkIP + }; - // server = new Server(options, compiler); + server = new Server(options, compiler); - // await server.start(); + await server.start(); - // ({ page, browser } = await runBrowser()); + ({ page, browser } = await runBrowser()); - // page - // .on("console", message => { - // consoleMessages.push(message); - // }) - // .on("pageerror", error => { - // pageErrors.push(error); - // }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - // const response = await page.goto(`http://${networkIP}:${port1}/main.js`, { - // waitUntil: "networkidle0" - // }); + const response = await page.goto(`http://${networkIP}:${port1}/main.js`, { + waitUntil: "networkidle0" + }); - // if (!server.checkHeader(headers, "host")) { - // throw new Error("Validation didn't fail"); - // } + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } - // expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - // expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - // "console messages" - // ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - // expect(pageErrors).toMatchSnapshot("page errors"); - // }); + expect(pageErrors).toMatchSnapshot("page errors"); + }); - it("should always allow value of the `host` option from the `client.webSocketURL` option if options.allowedHosts is auto", async () => { - const options = { - allowedHosts: "auto", - port: port1, - client: { - webSocketURL: "ws://test.host:80" - } - }; + it("should always allow value of the `host` option from the `client.webSocketURL` option if options.allowedHosts is auto", async () => { + const options = { + allowedHosts: "auto", + port: port1, + client: { + webSocketURL: "ws://test.host:80" + } + }; - const headers = { - host: "test.host" - }; + const headers = { + host: "test.host" + }; - server = new Server(options, compiler); + server = new Server(options, compiler); - await server.start(); + await server.start(); - ({ page, browser } = await runBrowser()); + ({ page, browser } = await runBrowser()); - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); + expect(pageErrors).toMatchSnapshot("page errors"); + }); - it("should always allow any host if options.allowedHosts is all", async () => { - const options = { - allowedHosts: "all", - port: port1 - }; - const headers = { - host: "bad.host" - }; + it("should always allow any host if options.allowedHosts is all", async () => { + const options = { + allowedHosts: "all", + port: port1 + }; + const headers = { + host: "bad.host" + }; - server = new Server(options, compiler); + server = new Server(options, compiler); - await server.start(); + await server.start(); - ({ page, browser } = await runBrowser()); + ({ page, browser } = await runBrowser()); - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); + expect(pageErrors).toMatchSnapshot("page errors"); + }); - it("should allow hosts in allowedHosts", async () => { - const tests = ["test.host", "test2.host", "test3.host"]; - const options = { - allowedHosts: tests, - port: port1 - }; + it("should allow hosts in allowedHosts", async () => { + const tests = ["test.host", "test2.host", "test3.host"]; + const options = { + allowedHosts: tests, + port: port1 + }; - server = new Server(options, compiler); + server = new Server(options, compiler); - await server.start(); + await server.start(); - ({ page, browser } = await runBrowser()); + ({ page, browser } = await runBrowser()); - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); - tests.forEach(test => { - const headers = { host: test }; + tests.forEach(test => { + const headers = { host: test }; - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } - }); + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); + expect(pageErrors).toMatchSnapshot("page errors"); + }); - it("should allow hosts that pass a wildcard in allowedHosts", async () => { - const options = { - allowedHosts: [".example.com"], - port: port1 - }; + it("should allow hosts that pass a wildcard in allowedHosts", async () => { + const options = { + allowedHosts: [".example.com"], + port: port1 + }; - server = new Server(options, compiler); + server = new Server(options, compiler); - await server.start(); + await server.start(); - ({ page, browser } = await runBrowser()); + ({ page, browser } = await runBrowser()); - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${port1}/main.js`, { + waitUntil: "networkidle0" + }); - const tests = [ - "www.example.com", - "subdomain.example.com", - "example.com", - "subsubcomain.subdomain.example.com", - "example.com:80", - "subdomain.example.com:80" - ]; + const tests = [ + "www.example.com", + "subdomain.example.com", + "example.com", + "subsubcomain.subdomain.example.com", + "example.com:80", + "subdomain.example.com:80" + ]; - tests.forEach(test => { - const headers = { host: test }; + tests.forEach(test => { + const headers = { host: test }; - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't fail"); - } - }); + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't fail"); + } + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); - }); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); }); diff --git a/packages/rspack-dev-server/tests/e2e/api.test.js b/packages/rspack-dev-server/tests/e2e/api.test.js index be54773faf8..5344f68c8e6 100644 --- a/packages/rspack-dev-server/tests/e2e/api.test.js +++ b/packages/rspack-dev-server/tests/e2e/api.test.js @@ -9,745 +9,745 @@ const sessionSubscribe = require("../helpers/session-subscribe"); const port = require("../helpers/ports-map").api; describe("API", () => { - describe("WEBPACK_SERVE environment variable", () => { - const OLD_ENV = process.env; - let server; - let page; - let browser; - let pageErrors; - let consoleMessages; - - beforeEach(async () => { - // this is important - it clears the cache - jest.resetModules(); - - process.env = { ...OLD_ENV }; - - delete process.env.WEBPACK_SERVE; - - ({ page, browser } = await runBrowser()); - - pageErrors = []; - consoleMessages = []; - }); - - afterEach(async () => { - await browser.close(); - await server.stop(); - process.env = OLD_ENV; - }); - - it("should be present", async () => { - expect(process.env.WEBPACK_SERVE).toBeUndefined(); - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - const { - RspackDevServer: WebpackDevServer - } = require("@rspack/dev-server"); - - const compiler = webpack(config); - server = new WebpackDevServer({ port }, compiler); - - await server.start(); - - expect(process.env.WEBPACK_SERVE).toBe("true"); - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); - }); - - describe("latest async API", () => { - it(`should work with async API`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await server.start(); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - - it(`should work with callback API`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await new Promise(resolve => { - server.startCallback(() => { - resolve(); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }); - } - }); - - it(`should catch errors within startCallback`, async () => { - const compiler = webpack(config); - const server = new Server( - { port, static: "https://absolute-url.com/somewhere" }, - compiler - ); - - await new Promise(resolve => { - server.startCallback(err => { - expect(err.message).toEqual( - "Using a URL as static.directory is not supported" - ); - resolve(); - }); - }); - - await new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }); - }); - - it(`should work when using configured manually`, async () => { - const compiler = webpack({ - ...config, - entry: [ - "@rspack/core/hot/dev-server.js", - `@rspack/dev-server/client/index.js?hot=true&live-reload=true"`, - path.resolve(__dirname, "../fixtures/client-config/foo.js") - ], - plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()] - }); - const server = new Server({ port, hot: false, client: false }, compiler); - - await server.start(); - - const { page, browser } = await runBrowser(); - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - - it(`should work and allow to rerun dev server multiple times`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await server.start(); - - const { page: firstPage, browser } = await runBrowser(); - - try { - const firstPageErrors = []; - const firstConsoleMessages = []; - - firstPage - .on("console", message => { - firstConsoleMessages.push(message); - }) - .on("pageerror", error => { - firstPageErrors.push(error); - }); - - await firstPage.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect( - firstConsoleMessages.map(message => message.text()) - ).toMatchSnapshot("console messages"); - expect(firstPageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await server.stop(); - } - - await server.start(); - - const secondPage = await runBrowser.runPage(browser); - - try { - // const secondPageErrors = []; - // const secondConsoleMessages = []; - // secondPage - // .on("console", (message) => { - // secondConsoleMessages.push(message); - // }) - // .on("pageerror", (error) => { - // secondPageErrors.push(error); - // }); - // await secondPage.goto(`http://127.0.0.1:${port}/`, { - // waitUntil: "networkidle0", - // }); - // expect( - // secondConsoleMessages.map((message) => message.text()), - // ).toMatchSnapshot("console messages"); - // expect(secondPageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - }); - - describe("Invalidate callback", () => { - let compiler; - let server; - let page; - let browser; - let pageErrors; - let consoleMessages; - - beforeEach(async () => { - compiler = webpack(config); - - ({ page, browser } = await runBrowser()); - - pageErrors = []; - consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - server = new Server({ port, static: false }, compiler); - - await server.start(); - }); - - afterEach(async () => { - await browser.close(); - await server.stop(); - }); - - it("should use the default `noop` callback when invalidate is called without any callback", async () => { - const callback = jest.fn(); - - server.invalidate(); - server.middleware.context.callbacks[0] = callback; - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(callback).toHaveBeenCalledTimes(1); - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); - - it("should use the provided `callback` function", async () => { - const callback = jest.fn(); - - server.invalidate(callback); - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(callback).toHaveBeenCalledTimes(1); - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); - }); - - describe("Server.getFreePort", () => { - let dummyServers = []; - let devServerPort; - - afterEach(() => { - delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; - delete process.env.WEBPACK_DEV_SERVER_PORT_RETRY; - - return dummyServers - .reduce( - (p, server) => - p.then( - () => - new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }) - ), - Promise.resolve() - ) - .then(() => { - dummyServers = []; - }); - }); + describe("WEBPACK_SERVE environment variable", () => { + const OLD_ENV = process.env; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + // this is important - it clears the cache + jest.resetModules(); + + process.env = { ...OLD_ENV }; + + delete process.env.WEBPACK_SERVE; + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + process.env = OLD_ENV; + }); + + it("should be present", async () => { + expect(process.env.WEBPACK_SERVE).toBeUndefined(); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const { + RspackDevServer: WebpackDevServer + } = require("@rspack/dev-server"); + + const compiler = webpack(config); + server = new WebpackDevServer({ port }, compiler); + + await server.start(); + + expect(process.env.WEBPACK_SERVE).toBe("true"); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("latest async API", () => { + it(`should work with async API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with callback API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await new Promise(resolve => { + server.startCallback(() => { + resolve(); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + } + }); + + it(`should catch errors within startCallback`, async () => { + const compiler = webpack(config); + const server = new Server( + { port, static: "https://absolute-url.com/somewhere" }, + compiler + ); + + await new Promise(resolve => { + server.startCallback(err => { + expect(err.message).toEqual( + "Using a URL as static.directory is not supported" + ); + resolve(); + }); + }); + + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + }); + + it(`should work when using configured manually`, async () => { + const compiler = webpack({ + ...config, + entry: [ + "@rspack/core/hot/dev-server.js", + `@rspack/dev-server/client/index.js?hot=true&live-reload=true"`, + path.resolve(__dirname, "../fixtures/client-config/foo.js") + ], + plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()] + }); + const server = new Server({ port, hot: false, client: false }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work and allow to rerun dev server multiple times`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page: firstPage, browser } = await runBrowser(); + + try { + const firstPageErrors = []; + const firstConsoleMessages = []; + + firstPage + .on("console", message => { + firstConsoleMessages.push(message); + }) + .on("pageerror", error => { + firstPageErrors.push(error); + }); + + await firstPage.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + firstConsoleMessages.map(message => message.text()) + ).toMatchSnapshot("console messages"); + expect(firstPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + await server.start(); + + const secondPage = await runBrowser.runPage(browser); + + try { + // const secondPageErrors = []; + // const secondConsoleMessages = []; + // secondPage + // .on("console", (message) => { + // secondConsoleMessages.push(message); + // }) + // .on("pageerror", (error) => { + // secondPageErrors.push(error); + // }); + // await secondPage.goto(`http://127.0.0.1:${port}/`, { + // waitUntil: "networkidle0", + // }); + // expect( + // secondConsoleMessages.map((message) => message.text()), + // ).toMatchSnapshot("console messages"); + // expect(secondPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); + + describe("Invalidate callback", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + server = new Server({ port, static: false }, compiler); + + await server.start(); + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the default `noop` callback when invalidate is called without any callback", async () => { + const callback = jest.fn(); + + server.invalidate(); + server.middleware.context.callbacks[0] = callback; + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should use the provided `callback` function", async () => { + const callback = jest.fn(); + + server.invalidate(callback); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("Server.getFreePort", () => { + let dummyServers = []; + let devServerPort; + + afterEach(() => { + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + delete process.env.WEBPACK_DEV_SERVER_PORT_RETRY; + + return dummyServers + .reduce( + (p, server) => + p.then( + () => + new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ) + .then(() => { + dummyServers = []; + }); + }); - function createDummyServers(n) { - process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; + function createDummyServers(n) { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; - return (Array.isArray(n) ? n : [...new Array(n)]).reduce( - (p, _, i) => - p.then( - () => - new Promise(resolve => { - devServerPort = 60000 + i; - const compiler = webpack(config); - const server = new Server( - { port: devServerPort, host: "0.0.0.0" }, - compiler - ); + return (Array.isArray(n) ? n : [...new Array(n)]).reduce( + (p, _, i) => + p.then( + () => + new Promise(resolve => { + devServerPort = 60000 + i; + const compiler = webpack(config); + const server = new Server( + { port: devServerPort, host: "0.0.0.0" }, + compiler + ); - dummyServers.push(server); + dummyServers.push(server); - server.startCallback(() => { - resolve(); - }); - }) - ), - Promise.resolve() - ); - } + server.startCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ); + } - it("should return the port when the port is specified", async () => { - const retryCount = 1; + it("should return the port when the port is specified", async () => { + const retryCount = 1; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - const freePort = await Server.getFreePort(9082); + const freePort = await Server.getFreePort(9082); - expect(freePort).toEqual(9082); - }); + expect(freePort).toEqual(9082); + }); - it("should return the port when the port is `null`", async () => { - const retryCount = 2; + it("should return the port when the port is `null`", async () => { + const retryCount = 2; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(null); + const freePort = await Server.getFreePort(null); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should return the port when the port is undefined", async () => { - const retryCount = 3; + it("should return the port when the port is undefined", async () => { + const retryCount = 3; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - // eslint-disable-next-line no-undefined - const freePort = await Server.getFreePort(undefined); + // eslint-disable-next-line no-undefined + const freePort = await Server.getFreePort(undefined); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port for up to defaultPortRetry times (number)", async () => { - const retryCount = 4; + it("should retry finding the port for up to defaultPortRetry times (number)", async () => { + const retryCount = 4; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port for up to defaultPortRetry times (string)", async () => { - const retryCount = 5; + it("should retry finding the port for up to defaultPortRetry times (string)", async () => { + const retryCount = 5; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port when serial ports are busy", async () => { - const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; + it("should retry finding the port when serial ports are busy", async () => { + const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; - await createDummyServers(busyPorts); + await createDummyServers(busyPorts); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toBeGreaterThan(60005); + expect(freePort).toBeGreaterThan(60005); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - try { - const pageErrors = []; - const consoleMessages = []; + try { + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - } - }); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + }); - it("should throw the error when the port isn't found", async () => { - expect.assertions(1); + it("should throw the error when the port isn't found", async () => { + expect.assertions(1); - jest.mock( - "webpack-dev-server/lib/getPort", - () => () => Promise.reject(new Error("busy")) - ); + jest.mock( + "webpack-dev-server/lib/getPort", + () => () => Promise.reject(new Error("busy")) + ); - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1; - try { - await Server.getFreePort(); - } catch (error) { - expect(error.message).toMatchSnapshot(); - } - }); - }); + try { + await Server.getFreePort(); + } catch (error) { + expect(error.message).toMatchSnapshot(); + } + }); + }); - describe("Server.checkHostHeader", () => { - it("should allow access for every requests using an IP", () => { - const options = {}; + describe("Server.checkHostHeader", () => { + it("should allow access for every requests using an IP", () => { + const options = {}; - const tests = [ - "192.168.1.123", - "192.168.1.2:8080", - "[::1]", - "[::1]:8080", - "[ad42::1de2:54c2:c2fa:1234]", - "[ad42::1de2:54c2:c2fa:1234]:8080" - ]; - - const compiler = webpack(config); - const server = new Server(options, compiler); - - tests.forEach(test => { - const headers = { host: test }; - - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't pass"); - } - }); - }); - - // it('should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object', async () => { - // const options = { - // port, - // client: { - // reconnect: false, - // webSocketURL: { - // hostname: "test.host" - // } - // }, - // webSocketServer: "ws" - // }; - // const headers = { - // origin: "https://test.host" - // }; - - // const compiler = webpack(config); - // const server = new Server(options, compiler); - - // await server.start(); - - // const { page, browser } = await runBrowser(); - - // try { - // const pageErrors = []; - // const consoleMessages = []; - - // page - // .on("console", message => { - // consoleMessages.push(message); - // }) - // .on("pageerror", error => { - // pageErrors.push(error); - // }); - - // const webSocketRequests = []; - // const session = await page.target().createCDPSession(); - - // session.on("Network.webSocketCreated", test => { - // webSocketRequests.push(test); - // }); - - // await session.send("Target.setAutoAttach", { - // autoAttach: true, - // flatten: true, - // waitForDebuggerOnStart: true - // }); - - // sessionSubscribe(session); - - // const response = await page.goto(`http://127.0.0.1:${port}/`, { - // waitUntil: "networkidle0" - // }); - - // if (!server.checkHeader(headers, "origin")) { - // throw new Error("Validation didn't fail"); - // } - - // await new Promise(resolve => { - // const interval = setInterval(() => { - // const needFinish = consoleMessages.filter(message => - // /Trying to reconnect/.test(message.text()) - // ); - - // if (needFinish.length > 0) { - // clearInterval(interval); - // resolve(); - // } - // }, 100); - // }); - - // expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); - - // expect(response.status()).toMatchSnapshot("response status"); - - // expect( - // // net::ERR_NAME_NOT_RESOLVED can be multiple times - // consoleMessages.map(message => message.text()).slice(0, 7) - // ).toMatchSnapshot("console messages"); - - // expect(pageErrors).toMatchSnapshot("page errors"); - // } catch (error) { - // throw error; - // } finally { - // await browser.close(); - // await server.stop(); - // } - // }); - }); + const tests = [ + "192.168.1.123", + "192.168.1.2:8080", + "[::1]", + "[::1]:8080", + "[ad42::1de2:54c2:c2fa:1234]", + "[ad42::1de2:54c2:c2fa:1234]:8080" + ]; + + const compiler = webpack(config); + const server = new Server(options, compiler); + + tests.forEach(test => { + const headers = { host: test }; + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't pass"); + } + }); + }); + + it('should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object', async () => { + const options = { + port, + client: { + reconnect: false, + webSocketURL: { + hostname: "test.host" + } + }, + webSocketServer: "ws" + }; + const headers = { + origin: "https://test.host" + }; + + const compiler = webpack(config); + const server = new Server(options, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "origin")) { + throw new Error("Validation didn't fail"); + } + + await new Promise(resolve => { + const interval = setInterval(() => { + const needFinish = consoleMessages.filter(message => + /Trying to reconnect/.test(message.text()) + ); + + if (needFinish.length > 0) { + clearInterval(interval); + resolve(); + } + }, 100); + }); + + expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect( + // net::ERR_NAME_NOT_RESOLVED can be multiple times + consoleMessages.map(message => message.text()).slice(0, 7) + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); }); diff --git a/packages/rspack-dev-server/tests/e2e/host.test.js b/packages/rspack-dev-server/tests/e2e/host.test.js new file mode 100644 index 00000000000..9adb12273fd --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/host.test.js @@ -0,0 +1,284 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").host; + +const ipv4 = Server.internalIPSync("v4"); +const ipv6 = Server.internalIPSync("v6"); +// macos requires root for using ip v6 +const isMacOS = process.platform === "darwin"; + +function getAddress(host, hostname) { + let address; + + if ( + typeof host === "undefined" || + (typeof host === "string" && host === "") + ) { + address = "::"; + } else if (typeof host === "string" && host === "0.0.0.0") { + address = "0.0.0.0"; + } else if (typeof host === "string" && host === "localhost") { + address = parseFloat(process.versions.node) >= 18 ? "::1" : "127.0.0.1"; + } else { + address = hostname; + } + + return { address }; +} + +describe("host", () => { + const hosts = [ + "", + // eslint-disable-next-line no-undefined + undefined, + "0.0.0.0", + "::", + "localhost", + "::1", + "127.0.0.1", + "local-ip", + "local-ipv4", + "local-ipv6" + ]; + + for (let host of hosts) { + it(`should work using "${host}" host and port as number`, async () => { + const compiler = webpack(config); + + if (!ipv6 || isMacOS) { + if (host === "::") { + host = "127.0.0.1"; + } else if (host === "::1") { + host = "127.0.0.1"; + } else if (host === "local-ipv6") { + host = "127.0.0.1"; + } + } + + const devServerOptions = { port }; + + if (host !== "") { + devServerOptions.host = host; + } + + const server = new Server(devServerOptions, compiler); + + let hostname = host; + + if (hostname === "0.0.0.0") { + hostname = "127.0.0.1"; + } else if ( + hostname === "" || + typeof hostname === "undefined" || + hostname === "::" || + hostname === "::1" + ) { + hostname = "[::1]"; + } else if (hostname === "local-ip" || hostname === "local-ipv4") { + hostname = ipv4; + } else if (hostname === "local-ipv6") { + hostname = `[${ipv6}]`; + } + + await server.start(); + + expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${hostname}:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work using "${host}" host and port as string`, async () => { + const compiler = webpack(config); + + if (!ipv6 || isMacOS) { + if (host === "::") { + host = "127.0.0.1"; + } else if (host === "::1") { + host = "127.0.0.1"; + } else if (host === "local-ipv6") { + host = "127.0.0.1"; + } + } + + const devServerOptions = { port: `${port}` }; + + if (host !== "") { + devServerOptions.host = host; + } + + const server = new Server(devServerOptions, compiler); + + let hostname = host; + + if (hostname === "0.0.0.0") { + hostname = "127.0.0.1"; + } else if ( + hostname === "" || + typeof hostname === "undefined" || + hostname === "::" || + hostname === "::1" + ) { + hostname = "[::1]"; + } else if (hostname === "local-ip" || hostname === "local-ipv4") { + hostname = ipv4; + } else if (hostname === "local-ipv6") { + hostname = `[${ipv6}]`; + } + + await server.start(); + + expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${hostname}:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work using "${host}" host and "auto" port`, async () => { + const compiler = webpack(config); + + process.env.WEBPACK_DEV_SERVER_BASE_PORT = port; + + if (!ipv6 || isMacOS) { + if (host === "::") { + host = "127.0.0.1"; + } else if (host === "::1") { + host = "127.0.0.1"; + } else if (host === "local-ipv6") { + host = "127.0.0.1"; + } + } + + const devServerOptions = { port: "auto" }; + + if (host !== "") { + devServerOptions.host = host; + } + + const server = new Server(devServerOptions, compiler); + + let hostname = host; + + if (hostname === "0.0.0.0") { + hostname = "127.0.0.1"; + } else if ( + hostname === "" || + typeof hostname === "undefined" || + hostname === "::" || + hostname === "::1" + ) { + hostname = "[::1]"; + } else if (hostname === "local-ip" || hostname === "local-ipv4") { + hostname = ipv4; + } else if (hostname === "local-ipv6") { + hostname = `[${ipv6}]`; + } + + await server.start(); + + expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + + const address = server.server.address(); + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://${hostname}:${address.port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + + await browser.close(); + await server.stop(); + } + }); + } + + // TODO need test on error + // it(`should throw an error on invalid host`, async () => { + // const compiler = webpack(config); + // const server = new Server({ port, host: "unknown.unknown" }, compiler); + // const runDevServer = async () => { + // await server.start(); + // }; + // + // return expect(runDevServer()).toBeDefined(); + // }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/hot-and-live-reload.test.js b/packages/rspack-dev-server/tests/e2e/hot-and-live-reload.test.js new file mode 100644 index 00000000000..8d4192b829f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/hot-and-live-reload.test.js @@ -0,0 +1,898 @@ +/** + * @jest-environment node + */ + +"use strict"; + +const path = require("path"); +const WebSocket = require("ws"); +const SockJS = require("sockjs-client"); +const webpack = require("@rspack/core"); +const fs = require("graceful-fs"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const HTMLGeneratorPlugin = require("../helpers/html-generator-plugin"); +const reloadConfig = require("../fixtures/reload-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["hot-and-live-reload"]; +const config = require("../fixtures/client-config/webpack.config"); +const multiCompilerConfig = require("../fixtures/multi-compiler-one-configuration/webpack.config"); + +const cssFilePath = path.resolve( + __dirname, + "../fixtures/reload-config/main.css" +); + +const INVALID_MESSAGE = "[webpack-dev-server] App updated. Recompiling..."; + +describe("hot and live reload", () => { + // "sockjs" client cannot add additional headers + const modes = [ + { + title: "should work and refresh content using hot module replacement" + }, + { + title: "should work and do nothing when web socket server disabled", + options: { + webSocketServer: false + } + }, + // Default web socket serve ("ws") + { + title: + "should work and refresh content using hot module replacement when hot enabled", + options: { + hot: true + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload enabled", + options: { + liveReload: true + } + }, + { + title: "should not refresh content when hot and no live reload disabled", + options: { + hot: false, + liveReload: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload disabled and hot enabled", + options: { + liveReload: false, + hot: true + } + }, + { + title: "should work and refresh content using live reload", + options: { + liveReload: true, + hot: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload enabled and hot disabled", + options: { + liveReload: true, + hot: true + } + }, + // "ws" web socket serve + { + title: + "should work and refresh content using hot module replacement when hot enabled", + options: { + webSocketServer: "ws", + hot: true + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload enabled", + options: { + webSocketServer: "ws", + liveReload: true + } + }, + { + title: "should not refresh content when hot and no live reload disabled", + options: { + webSocketServer: "ws", + hot: false, + liveReload: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload disabled and hot enabled", + options: { + webSocketServer: "ws", + liveReload: false, + hot: true + } + }, + { + title: + "should work and refresh content using live reload when live reload enabled and hot disabled", + options: { + webSocketServer: "ws", + liveReload: true, + hot: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload and hot enabled", + options: { + webSocketServer: "ws", + liveReload: true, + hot: true + } + }, + // "sockjs" web socket serve + { + title: + "should work and refresh content using hot module replacement when hot enabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + hot: true + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload enabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + liveReload: true + } + }, + { + title: "should not refresh content when hot and no live reload disabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + hot: false, + liveReload: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload disabled and hot enabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + liveReload: false, + hot: true + } + }, + { + title: + "should work and refresh content using live reload when live reload disabled and hot enabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + liveReload: true, + hot: false + } + }, + { + title: + "should work and refresh content using hot module replacement when live reload and hot enabled", + options: { + allowedHosts: "all", + + webSocketServer: "sockjs", + liveReload: true, + hot: true + } + }, + { + title: + 'should work and allow to disable hot module replacement using the "webpack-dev-server-hot=false"', + query: "?webpack-dev-server-hot=false", + options: { + liveReload: true, + hot: true + } + }, + { + title: + 'should work and allow to disable live reload using the "webpack-dev-server-live-reload=false"', + query: "?webpack-dev-server-live-reload=false", + options: { + liveReload: true, + hot: false + } + }, + { + title: + 'should work and allow to disable hot module replacement and live reload using the "webpack-dev-server-hot=false&webpack-dev-server-live-reload=false"', + query: + "?webpack-dev-server-hot=false&webpack-dev-server-live-reload=false", + options: { + liveReload: true, + hot: true + } + }, + { + title: "should work with manual client setup", + webpackOptions: { + entry: [ + require.resolve("@rspack/dev-server/client/index.js"), + require.resolve("../fixtures/reload-config/foo.js") + ] + }, + options: { + client: false, + liveReload: true, + hot: true + } + }, + // TODO we still output logs from webpack, need to improve this + { + title: + "should work with manual client setup and allow to enable hot module replacement", + webpackOptions: { + entry: [ + "@rspack/core/hot/dev-server", + `${require.resolve("@rspack/dev-server/client/index.js")}?hot=true`, + require.resolve("../fixtures/reload-config/foo.js") + ], + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new HTMLGeneratorPlugin() + ] + }, + options: { + client: false, + liveReload: false, + hot: false + } + }, + { + title: + "should work with manual client setup and allow to disable hot module replacement", + webpackOptions: { + entry: [ + `${require.resolve("@rspack/dev-server/client/index.js")}?hot=false`, + require.resolve("../fixtures/reload-config/foo.js") + ] + }, + options: { + client: false, + liveReload: true, + hot: true + } + }, + { + title: + "should work with manual client setup and allow to enable live reload", + webpackOptions: { + entry: [ + `${require.resolve("@rspack/dev-server/client/index.js")}?live-reload=true`, + require.resolve("../fixtures/reload-config/foo.js") + ] + }, + options: { + client: false, + liveReload: false, + hot: false + } + }, + { + title: + "should work with manual client setup and allow to disable live reload", + webpackOptions: { + entry: [ + `${require.resolve("@rspack/dev-server/client/index.js")}?live-reload=false`, + require.resolve("../fixtures/reload-config/foo.js") + ] + }, + options: { + client: false, + liveReload: true, + hot: false + } + } + ]; + + let browser; + let server; + + beforeEach(() => { + fs.writeFileSync(cssFilePath, "body { background-color: rgb(0, 0, 255); }"); + }); + + afterEach(async () => { + if (browser) { + await browser.close(); + } + + if (server) { + await server.stop(); + } + + fs.unlinkSync(cssFilePath); + }); + + modes.forEach(mode => { + const webSocketServerTitle = + mode.options && mode.options.webSocketServer + ? mode.options.webSocketServer + : "default"; + + it(`${mode.title} (${webSocketServerTitle})`, async () => { + const webpackOptions = { ...reloadConfig, ...mode.webpackOptions }; + const compiler = webpack(webpackOptions); + const testDevServerOptions = mode.options || {}; + const devServerOptions = { port, ...testDevServerOptions }; + + server = new Server(devServerOptions, compiler); + + await server.start(); + + const webSocketServerLaunched = + testDevServerOptions.webSocketServer !== false; + + await new Promise(resolve => { + const webSocketTransport = + typeof testDevServerOptions.webSocketServer !== "undefined" && + testDevServerOptions.webSocketServer !== false + ? testDevServerOptions.webSocketServer + : "ws"; + + if (webSocketTransport === "ws") { + const ws = new WebSocket( + `ws://127.0.0.1:${devServerOptions.port}/ws`, + { + headers: { + host: `127.0.0.1:${devServerOptions.port}`, + origin: `http://127.0.0.1:${devServerOptions.port}` + } + } + ); + + let opened = false; + let received = false; + let errored = false; + + ws.on("error", error => { + if (!webSocketServerLaunched && /404/.test(error)) { + errored = true; + } else { + errored = true; + } + + ws.close(); + }); + + ws.on("open", () => { + opened = true; + }); + + ws.on("message", data => { + const message = JSON.parse(data.toString()); + + if (message.type === "ok") { + received = true; + + ws.close(); + } + }); + + ws.on("close", () => { + if (opened && received && !errored) { + resolve(); + } else if (!webSocketServerLaunched && errored) { + resolve(); + } + }); + } else { + const sockjs = new SockJS( + `http://127.0.0.1:${devServerOptions.port}/ws` + ); + + let opened = false; + let received = false; + let errored = false; + + sockjs.onerror = () => { + errored = true; + }; + + sockjs.onopen = () => { + opened = true; + }; + + sockjs.onmessage = ({ data }) => { + const message = JSON.parse(data.toString()); + + if (message.type === "ok") { + received = true; + + sockjs.close(); + } + }; + + sockjs.onclose = event => { + if (opened && received && !errored) { + resolve(); + } else if (event && event.reason === "Cannot connect to server") { + resolve(); + } + }; + } + }); + + const launched = await runBrowser(); + + ({ browser } = launched); + + const page = launched.page; + + const consoleMessages = []; + const pageErrors = []; + + let doneHotUpdate = false; + let hasDisconnectedMessage = false; + + page + .on("console", message => { + if (!hasDisconnectedMessage) { + const text = message.text(); + + hasDisconnectedMessage = /Disconnected!/.test(text); + consoleMessages.push(text); + } + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", requestObj => { + if (/\.hot-update\.json$/.test(requestObj.url())) { + doneHotUpdate = true; + } + }); + + await page.goto(`http://localhost:${port}/${mode.query || ""}`, { + waitUntil: "networkidle0" + }); + + const backgroundColorBefore = await page.evaluate(() => { + const body = document.body; + + return getComputedStyle(body)["background-color"]; + }); + + expect(backgroundColorBefore).toEqual("rgb(0, 0, 255)"); + + fs.writeFileSync( + cssFilePath, + "body { background-color: rgb(255, 0, 0); }" + ); + + let waitHot = + typeof testDevServerOptions.hot !== "undefined" + ? testDevServerOptions.hot + : true; + let waitLiveReload = + typeof testDevServerOptions.liveReload !== "undefined" + ? testDevServerOptions.liveReload + : true; + + if (webSocketServerLaunched === false) { + waitHot = false; + waitLiveReload = false; + } + + if (Array.isArray(webpackOptions.entry)) { + if (webpackOptions.entry.some(item => item.includes("hot=true"))) { + waitHot = true; + } else if ( + webpackOptions.entry.some(item => item.includes("hot=false")) + ) { + waitHot = false; + } + } + + if (Array.isArray(webpackOptions.entry)) { + if ( + webpackOptions.entry.some(item => item.includes("live-reload=true")) + ) { + waitLiveReload = true; + } else if ( + webpackOptions.entry.some(item => item.includes("live-reload=false")) + ) { + waitLiveReload = false; + } + } + + const query = mode.query || ""; + + if (query.includes("webpack-dev-server-hot=false")) { + waitHot = false; + } + + if (query.includes("webpack-dev-server-live-reload=false")) { + waitLiveReload = false; + } + + if (waitHot) { + await page.waitForFunction( + () => + getComputedStyle(document.body)["background-color"] === + "rgb(255, 0, 0)" + ); + + expect(doneHotUpdate).toBe(true); + } else if (waitLiveReload) { + await page.waitForNavigation({ + waitUntil: "networkidle0" + }); + } else if (webSocketServerLaunched) { + await new Promise(resolve => { + const interval = setInterval(() => { + if (consoleMessages.includes(INVALID_MESSAGE)) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + } + + const backgroundColorAfter = await page.evaluate(() => { + const body = document.body; + + return getComputedStyle(body)["background-color"]; + }); + + if (!waitHot && !waitLiveReload) { + expect(backgroundColorAfter).toEqual("rgb(0, 0, 255)"); + } else { + expect(backgroundColorAfter).toEqual("rgb(255, 0, 0)"); + } + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); + +// the following cases check to make sure that the HMR +// plugin is actually added + +// describe("simple hot config HMR plugin", () => { +// let compiler; +// let server; +// let page; +// let browser; +// let pageErrors; +// let consoleMessages; + +// beforeEach(async () => { +// compiler = webpack(config); + +// ({ page, browser } = await runBrowser()); + +// pageErrors = []; +// consoleMessages = []; +// }); + +// afterEach(async () => { +// await browser.close(); +// await server.stop(); +// }); + +// it("should register the HMR plugin before compilation is complete", async () => { +// let pluginFound = false; + +// compiler.hooks.compilation.intercept({ +// register: (tapInfo) => { +// if (tapInfo.name === "HotModuleReplacementPlugin") { +// pluginFound = true; +// } + +// return tapInfo; +// }, +// }); + +// server = new Server({ port }, compiler); + +// await server.start(); + +// expect(pluginFound).toBe(true); + +// page +// .on("console", (message) => { +// consoleMessages.push(message); +// }) +// .on("pageerror", (error) => { +// pageErrors.push(error); +// }); + +// const response = await page.goto(`http://127.0.0.1:${port}/`, { +// waitUntil: "networkidle0", +// }); + +// expect(response.status()).toMatchSnapshot("response status"); + +// expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( +// "console messages", +// ); + +// expect(pageErrors).toMatchSnapshot("page errors"); +// }); +// }); + +// describe("simple hot config HMR plugin with already added HMR plugin", () => { +// let compiler; +// let server; +// let page; +// let browser; +// let pageErrors; +// let consoleMessages; + +// beforeEach(async () => { +// compiler = webpack({ +// ...config, +// plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()], +// }); + +// ({ page, browser } = await runBrowser()); + +// pageErrors = []; +// consoleMessages = []; +// }); + +// afterEach(async () => { +// await browser.close(); +// await server.stop(); +// }); + +// it("should register the HMR plugin before compilation is complete", async () => { +// let pluginFound = false; + +// compiler.hooks.compilation.intercept({ +// register: (tapInfo) => { +// if (tapInfo.name === "HotModuleReplacementPlugin") { +// pluginFound = true; +// } + +// return tapInfo; +// }, +// }); + +// server = new Server({ port }, compiler); + +// await server.start(); + +// expect(compiler.options.plugins).toHaveLength(2); +// expect(pluginFound).toBe(true); + +// page +// .on("console", (message) => { +// consoleMessages.push(message); +// }) +// .on("pageerror", (error) => { +// pageErrors.push(error); +// }); + +// const response = await page.goto(`http://127.0.0.1:${port}/`, { +// waitUntil: "networkidle0", +// }); + +// expect(response.status()).toMatchSnapshot("response status"); + +// expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( +// "console messages", +// ); + +// expect(pageErrors).toMatchSnapshot("page errors"); +// }); +// }); + +// describe("simple config with already added HMR plugin", () => { +// let loggerWarnSpy; +// let getInfrastructureLoggerSpy; +// let compiler; +// let server; + +// beforeEach(() => { +// compiler = webpack({ +// ...config, +// devServer: { hot: false }, +// plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()], +// }); + +// loggerWarnSpy = jest.fn(); + +// getInfrastructureLoggerSpy = jest +// .spyOn(compiler, "getInfrastructureLogger") +// .mockImplementation(() => { +// return { +// warn: loggerWarnSpy, +// info: () => { }, +// log: () => { }, +// }; +// }); +// }); + +// afterEach(() => { +// getInfrastructureLoggerSpy.mockRestore(); +// loggerWarnSpy.mockRestore(); +// }); + +// it("should show warning with hot normalized as true", async () => { +// server = new Server({ port }, compiler); + +// await server.start(); + +// expect(loggerWarnSpy).toHaveBeenCalledWith( +// `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`, +// ); + +// await server.stop(); +// }); + +// it(`should show warning with "hot: true"`, async () => { +// server = new Server({ port, hot: true }, compiler); + +// await server.start(); + +// expect(loggerWarnSpy).toHaveBeenCalledWith( +// `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`, +// ); + +// await server.stop(); +// }); + +// it(`should not show warning with "hot: false"`, async () => { +// server = new Server({ port, hot: false }, compiler); + +// await server.start(); + +// expect(loggerWarnSpy).not.toHaveBeenCalledWith( +// `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.`, +// ); + +// await server.stop(); +// }); +// }); + +// describe("multi compiler hot config HMR plugin", () => { +// let compiler; +// let server; +// let page; +// let browser; +// let pageErrors; +// let consoleMessages; + +// beforeEach(async () => { +// compiler = webpack(multiCompilerConfig); + +// ({ page, browser } = await runBrowser()); + +// pageErrors = []; +// consoleMessages = []; +// }); + +// afterEach(async () => { +// await browser.close(); +// await server.stop(); +// }); + +// it("should register the HMR plugin before compilation is complete", async () => { +// let pluginFound = false; + +// compiler.compilers[0].hooks.compilation.intercept({ +// register: (tapInfo) => { +// if (tapInfo.name === "HotModuleReplacementPlugin") { +// pluginFound = true; +// } + +// return tapInfo; +// }, +// }); + +// server = new Server({ port }, compiler); + +// await server.start(); + +// expect(pluginFound).toBe(true); + +// page +// .on("console", (message) => { +// consoleMessages.push(message); +// }) +// .on("pageerror", (error) => { +// pageErrors.push(error); +// }); + +// const response = await page.goto(`http://127.0.0.1:${port}/`, { +// waitUntil: "networkidle0", +// }); + +// expect(response.status()).toMatchSnapshot("response status"); + +// expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( +// "console messages", +// ); + +// expect(pageErrors).toMatchSnapshot("page errors"); +// }); +// }); + +// describe("hot disabled HMR plugin", () => { +// let compiler; +// let server; +// let page; +// let browser; +// let pageErrors; +// let consoleMessages; + +// beforeEach(async () => { +// compiler = webpack(config); + +// ({ page, browser } = await runBrowser()); + +// pageErrors = []; +// consoleMessages = []; +// }); + +// afterEach(async () => { +// await browser.close(); +// await server.stop(); +// }); + +// it("should NOT register the HMR plugin before compilation is complete", async () => { +// let pluginFound = false; + +// compiler.hooks.compilation.intercept({ +// register: (tapInfo) => { +// if (tapInfo.name === "HotModuleReplacementPlugin") { +// pluginFound = true; +// } + +// return tapInfo; +// }, +// }); + +// server = new Server({ port, hot: false }, compiler); + +// await server.start(); + +// expect(pluginFound).toBe(false); + +// page +// .on("console", (message) => { +// consoleMessages.push(message); +// }) +// .on("pageerror", (error) => { +// pageErrors.push(error); +// }); + +// const response = await page.goto(`http://127.0.0.1:${port}/`, { +// waitUntil: "networkidle0", +// }); + +// expect(response.status()).toMatchSnapshot("response status"); + +// expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( +// "console messages", +// ); + +// expect(pageErrors).toMatchSnapshot("page errors"); +// }); +// }); diff --git a/packages/rspack-dev-server/tests/e2e/ipc.test.js b/packages/rspack-dev-server/tests/e2e/ipc.test.js new file mode 100644 index 00000000000..0098b5efee8 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/ipc.test.js @@ -0,0 +1,354 @@ +"use strict"; + +const os = require("os"); +const net = require("net"); +const path = require("path"); +const http = require("http"); +const webpack = require("@rspack/core"); +const httpProxy = require("http-proxy"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const sessionSubscribe = require("../helpers/session-subscribe"); +const port1 = require("../helpers/ports-map").ipc; + +const webSocketServers = ["ws", "sockjs"]; + +describe("web socket server URL", () => { + for (const webSocketServer of webSocketServers) { + const websocketURLProtocol = webSocketServer === "ws" ? "ws" : "http"; + + it(`should work with the "ipc" option using "true" value ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const proxyHost = devServerHost; + const proxyPort = port1; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + ipc: true + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const proxy = httpProxy.createProxyServer({ + target: { socketPath: server.options.ipc } + }); + + const proxyServer = http.createServer((request, response) => { + // You can define here your custom logic to handle the request + // and then proxy the request. + proxy.web(request, response); + }); + + proxyServer.on("upgrade", (request, socket, head) => { + proxy.ws(request, socket, head); + }); + + return proxyServer.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${proxyPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "ipc" option using "string" value ("${webSocketServer}")`, async () => { + const isWindows = process.platform === "win32"; + const pipePrefix = isWindows ? "\\\\.\\pipe\\" : os.tmpdir(); + const pipeName = `webpack-dev-server.${process.pid}-1.sock`; + const ipc = path.join(pipePrefix, pipeName); + + const devServerHost = "127.0.0.1"; + const proxyHost = devServerHost; + const proxyPort = port1; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + ipc + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const proxy = httpProxy.createProxyServer({ + target: { socketPath: ipc } + }); + + const proxyServer = http.createServer((request, response) => { + // You can define here your custom logic to handle the request + // and then proxy the request. + proxy.web(request, response); + }); + + proxyServer.on("upgrade", (request, socket, head) => { + proxy.ws(request, socket, head); + }); + + return proxyServer.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${proxyPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + // TODO un skip after implement new API + it.skip(`should work with the "ipc" option using "string" value and remove old ("${webSocketServer}")`, async () => { + const isWindows = process.platform === "win32"; + const localRelative = path.relative(process.cwd(), `${os.tmpdir()}/`); + const pipePrefix = isWindows ? "\\\\.\\pipe\\" : localRelative; + const pipeName = `webpack-dev-server.${process.pid}-2.sock`; + const ipc = path.join(pipePrefix, pipeName); + + const ipcServer = await new Promise((resolve, reject) => { + const server = net.Server(); + + server.on("error", error => { + reject(error); + }); + + return server.listen(ipc, () => { + resolve(); + }); + }); + + const devServerHost = "127.0.0.1"; + const proxyHost = devServerHost; + const proxyPort = port1; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + host: devServerHost, + ipc + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const proxy = httpProxy.createProxyServer({ + target: { socketPath: ipc } + }); + + const proxyServer = http.createServer((request, response) => { + // You can define here your custom logic to handle the request + // and then proxy the request. + proxy.web(request, response); + }); + + proxyServer.on("upgrade", (request, socket, head) => { + proxy.ws(request, socket, head); + }); + + return proxyServer.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${proxyPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await new Promise((resolve, reject) => { + ipcServer.close(error => { + if (error) { + reject(error); + + return; + } + + resolve(); + }); + }); + await browser.close(); + await server.stop(); + } + }); + } +}); diff --git a/packages/rspack-dev-server/tests/e2e/lazy-compilation.test.js b/packages/rspack-dev-server/tests/e2e/lazy-compilation.test.js new file mode 100644 index 00000000000..cb51ef0e59f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/lazy-compilation.test.js @@ -0,0 +1,112 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const lazyCompilationSingleEntryConfig = require("../fixtures/lazy-compilation-single-entry/webpack.config"); +const lazyCompilationMultipleEntriesConfig = require("../fixtures/lazy-compilation-multiple-entries/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["lazy-compilation"]; + +describe("lazy compilation", () => { + // TODO jest freeze due webpack do not close `eventsource`, we should uncomment this after fix it on webpack side + it.skip(`should work with single entry`, async () => { + const compiler = webpack(lazyCompilationSingleEntryConfig); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/test.html`, { + waitUntil: "domcontentloaded" + }); + await new Promise(resolve => { + const interval = setInterval(() => { + if (consoleMessages.includes("Hey.")) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it.skip(`should work with multiple entries`, async () => { + const compiler = webpack(lazyCompilationMultipleEntriesConfig); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/test-one.html`, { + waitUntil: "domcontentloaded" + }); + await new Promise(resolve => { + const interval = setInterval(() => { + console.log(consoleMessages); + if (consoleMessages.includes("One.")) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + await page.goto(`http://127.0.0.1:${port}/test-two.html`, { + waitUntil: "domcontentloaded" + }); + await new Promise(resolve => { + const interval = setInterval(() => { + console.log(consoleMessages); + if (consoleMessages.includes("Two.")) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/logging.test.js b/packages/rspack-dev-server/tests/e2e/logging.test.js new file mode 100644 index 00000000000..cefbc0f98fa --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/logging.test.js @@ -0,0 +1,242 @@ +"use strict"; + +const path = require("path"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const HTMLGeneratorPlugin = require("../helpers/html-generator-plugin"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").logging; +require("../helpers/normalize"); + +describe("logging", () => { + const webSocketServers = [ + { webSocketServer: "ws" }, + { webSocketServer: "sockjs" } + ]; + + const cases = [ + { + title: "should work and log message about live reloading is enabled", + devServerOptions: { + hot: false + } + }, + { + title: + "should work and log messages about hot and live reloading is enabled", + devServerOptions: { + hot: true + } + }, + { + title: "should work and log messages about hot is enabled", + devServerOptions: { + liveReload: false + } + }, + { + title: + "should work and log messages about hot and live reloading is enabled", + devServerOptions: { + liveReload: true + } + }, + { + title: + "should work and do not log messages about hot and live reloading is enabled", + devServerOptions: { + liveReload: false, + hot: false + } + }, + { + title: + "should work and log messages about hot and live reloading is enabled", + devServerOptions: { + liveReload: true, + hot: true + } + }, + { + title: "should work and log warnings by default", + webpackOptions: { + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + compilation.warnings.push( + new Error("Warning from compilation") + ); + } + ); + } + }, + new HTMLGeneratorPlugin() + ] + } + }, + { + title: "should work and log errors by default", + webpackOptions: { + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + compilation.errors.push(new Error("Error from compilation")); + } + ); + } + }, + new HTMLGeneratorPlugin() + ] + } + }, + { + title: 'should work when the "client.logging" is "info"', + devServerOptions: { + client: { + logging: "info" + } + } + }, + { + title: 'should work when the "client.logging" is "log"', + devServerOptions: { + client: { + logging: "log" + } + } + }, + { + title: 'should work when the "client.logging" is "verbose"', + devServerOptions: { + client: { + logging: "verbose" + } + } + }, + { + title: 'should work when the "client.logging" is "none"', + devServerOptions: { + client: { + logging: "none" + } + } + }, + { + title: "should work and log only error", + webpackOptions: { + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + compilation.warnings.push( + new Error("Warning from compilation") + ); + compilation.errors.push(new Error("Error from compilation")); + } + ); + } + }, + new HTMLGeneratorPlugin() + ] + }, + devServerOptions: { + client: { + logging: "error" + } + } + }, + { + title: "should work and log warning and errors", + webpackOptions: { + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + compilation.warnings.push( + new Error("Warning from compilation") + ); + compilation.errors.push(new Error("Error from compilation")); + } + ); + } + }, + new HTMLGeneratorPlugin() + ] + }, + devServerOptions: { + client: { + logging: "warn" + } + } + }, + { + title: "should work and log static changes", + devServerOptions: { + static: path.resolve(__dirname, "../fixtures/client-config/static") + } + } + ]; + + webSocketServers.forEach(webSocketServer => { + cases.forEach(testCase => { + it(`${testCase.title} (${ + webSocketServer.webSocketServer || "default" + })`, async () => { + const compiler = webpack({ ...config, ...testCase.webpackOptions }); + const devServerOptions = { + port, + ...testCase.devServerOptions + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + if (testCase.devServerOptions && testCase.devServerOptions.static) { + fs.writeFileSync( + path.join(testCase.devServerOptions.static, "./foo.txt"), + "Text" + ); + + await page.waitForNavigation({ + waitUntil: "networkidle0" + }); + } + + expect( + consoleMessages.map(message => message.text().replace(/\\/g, "/")) + ).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/mime-types.test.js b/packages/rspack-dev-server/tests/e2e/mime-types.test.js new file mode 100644 index 00000000000..80005919c26 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/mime-types.test.js @@ -0,0 +1,135 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/mime-types-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["mime-types-option"]; + +describe("mimeTypes option", () => { + describe("as an object with a remapped type", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + devMiddleware: { + mimeTypes: { + js: "text/plain" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should request file with different js mime type", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("as an object with a custom type", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + devMiddleware: { + mimeTypes: { + custom: "text/html" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should request file with different js mime type", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/file.custom`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/module-federation.test.js b/packages/rspack-dev-server/tests/e2e/module-federation.test.js new file mode 100644 index 00000000000..20b86eb8b6a --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/module-federation.test.js @@ -0,0 +1,296 @@ +"use strict"; + +const requireFromString = require("require-from-string"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const simpleConfig = require("../fixtures/module-federation-config/webpack.config"); +const objectEntryConfig = require("../fixtures/module-federation-config/webpack.object-entry.config"); +const multiConfig = require("../fixtures/module-federation-config/webpack.multi.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["module-federation"]; +const pluginConfig = require("../fixtures/module-federation-config/webpack.plugin"); + +describe("Module federation", () => { + describe("should work with simple multi-entry config", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(simpleConfig); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the last entry export", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + const textContent = await response.text(); + + expect(textContent).toContain("entry1"); + + let exports; + + expect(() => { + exports = requireFromString(textContent); + }).not.toThrow(); + + expect(exports).toEqual("entry2"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("should work with object multi-entry config", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(objectEntryConfig); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the last entry export", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + const textContent = await response.text(); + + expect(textContent).toContain("entry1"); + + let exports; + + expect(() => { + exports = requireFromString(textContent); + }).not.toThrow(); + + expect(exports).toEqual("entry2"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should support the named entry export", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo.js`, { + waitUntil: "networkidle0" + }); + + const textContent = await response.text(); + + expect(textContent).not.toContain("entry2"); + + let exports; + + expect(() => { + exports = requireFromString(textContent); + }).not.toThrow(); + + expect(exports).toEqual("entry1"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("should work with multi compiler config", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(multiConfig); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the last entry export", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + const textContent = await response.text(); + + expect(textContent).toContain("entry1"); + + let exports; + + expect(() => { + exports = requireFromString(textContent); + }).not.toThrow(); + + expect(exports).toEqual("entry2"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("should use plugin", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(pluginConfig); + server = new Server({ port }, compiler); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should contain hot script in remoteEntry.js", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/remoteEntry.js`, + { + waitUntil: "networkidle0" + } + ); + + const remoteEntryTextContent = await response.text(); + + expect(remoteEntryTextContent).toMatch(/rspack\/hot\/dev-server\.js/); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should contain hot script in main.js", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + const mainEntryTextContent = await response.text(); + + expect(mainEntryTextContent).toMatch(/rspack\/hot\/dev-server\.js/); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/multi-compiler.test.js b/packages/rspack-dev-server/tests/e2e/multi-compiler.test.js new file mode 100644 index 00000000000..38575657cbf --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/multi-compiler.test.js @@ -0,0 +1,778 @@ +"use strict"; + +const path = require("path"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const oneWebTargetConfiguration = require("../fixtures/multi-compiler-one-configuration/webpack.config"); +const twoWebTargetConfiguration = require("../fixtures/multi-compiler-two-configurations/webpack.config"); +const universalConfiguration = require("../fixtures/universal-compiler-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["multi-compiler"]; + +describe("multi compiler", () => { + it(`should work with one web target configuration and do nothing`, async () => { + const compiler = webpack(oneWebTargetConfiguration); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with web target configurations and do nothing`, async () => { + const compiler = webpack(twoWebTargetConfiguration); + const devServerOptions = { + port + }; + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/one-main.html`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/two-main.html`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with web target configurations when hot and live reloads are enabled, and do hot reload by default when changing own entries`, async () => { + const compiler = webpack(twoWebTargetConfiguration); + const devServerOptions = { + port, + hot: true, + liveReload: true + }; + const pathToOneEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/one.js" + ); + const originalOneEntryContent = fs.readFileSync(pathToOneEntry); + const pathToTwoEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/two.js" + ); + const originalTwoEntryContent = fs.readFileSync(pathToTwoEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + let text = message.text(); + + if (/Error: Aborted because/.test(text)) { + const splittedText = text.split("\n"); + + text = `${splittedText[0]}\n${splittedText[1]}\n `; + } + + consoleMessages.push(text); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/one-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToOneEntry, `${originalOneEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/two-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToTwoEntry, `${originalTwoEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToOneEntry, originalOneEntryContent); + fs.writeFileSync(pathToTwoEntry, originalTwoEntryContent); + } + }); + + it(`should work with web target configurations when only hot reload is enabled, and do hot reload when changing own entries`, async () => { + const compiler = webpack(twoWebTargetConfiguration); + const devServerOptions = { + port, + hot: true, + liveReload: false + }; + const pathToOneEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/one.js" + ); + const originalOneEntryContent = fs.readFileSync(pathToOneEntry); + const pathToTwoEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/two.js" + ); + const originalTwoEntryContent = fs.readFileSync(pathToTwoEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + let text = message.text(); + + if (/Error: Aborted because/.test(text)) { + const splittedText = text.split("\n"); + + text = `${splittedText[0]}\n${splittedText[1]}\n `; + } + + consoleMessages.push(text); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/one-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToOneEntry, `${originalOneEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/two-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToTwoEntry, `${originalTwoEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToOneEntry, originalOneEntryContent); + fs.writeFileSync(pathToTwoEntry, originalTwoEntryContent); + } + }); + + it(`should work with web target configurations when only live reload is enabled, and do live reload when changing own entries`, async () => { + const compiler = webpack(twoWebTargetConfiguration); + const devServerOptions = { + port, + hot: false, + liveReload: true + }; + const pathToOneEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/one.js" + ); + const originalOneEntryContent = fs.readFileSync(pathToOneEntry); + const pathToTwoEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/two.js" + ); + const originalTwoEntryContent = fs.readFileSync(pathToTwoEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/one-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToOneEntry, `${originalOneEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/two-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToTwoEntry, `${originalTwoEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToOneEntry, originalOneEntryContent); + fs.writeFileSync(pathToTwoEntry, originalTwoEntryContent); + } + }); + + it(`should work with web target configurations when only live reload is enabled and do live reload when changing other entries`, async () => { + const compiler = webpack(twoWebTargetConfiguration); + const devServerOptions = { + port, + hot: false, + liveReload: true + }; + const pathToOneEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/one.js" + ); + const originalOneEntryContent = fs.readFileSync(pathToOneEntry); + const pathToTwoEntry = path.resolve( + __dirname, + "../fixtures/multi-compiler-two-configurations/two.js" + ); + const originalTwoEntryContent = fs.readFileSync(pathToTwoEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/one-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToTwoEntry, `${originalTwoEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/two-main.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync(pathToOneEntry, `${originalOneEntryContent}// comment`); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToOneEntry, originalOneEntryContent); + fs.writeFileSync(pathToTwoEntry, originalTwoEntryContent); + } + }); + + it("should work with universal configuration and do nothing", async () => { + const compiler = webpack(universalConfiguration); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + const pageErrors = []; + const consoleMessages = []; + try { + const serverResponse = await page.goto( + `http://127.0.0.1:${port}/server.js`, + { + waitUntil: "networkidle0" + } + ); + + const serverResponseText = await serverResponse.text(); + + expect(serverResponseText).toContain("Hello from the server"); + expect(serverResponseText).not.toContain("WebsocketServer"); + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it(`should work with universal configuration when hot and live reloads are enabled, and do hot reload for browser compiler by default when browser entry changed`, async () => { + const compiler = webpack(universalConfiguration); + const devServerOptions = { + port, + hot: true, + liveReload: true + }; + const pathToBrowserEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/browser.js" + ); + const originalBrowserEntryContent = fs.readFileSync(pathToBrowserEntry); + const pathToServerEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/server.js" + ); + const originalServerEntryContent = fs.readFileSync(pathToServerEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const serverResponse = await page.goto( + `http://127.0.0.1:${port}/server.js`, + { + waitUntil: "networkidle0" + } + ); + + const serverResponseText = await serverResponse.text(); + + expect(serverResponseText).toContain("Hello from the server"); + expect(serverResponseText).not.toContain("WebsocketServer"); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + let text = message.text(); + + if (/Error: Aborted because/.test(text)) { + const splittedText = text.split("\n"); + + text = `${splittedText[0]}\n${splittedText[1]}\n `; + } + + consoleMessages.push(text); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToBrowserEntry, + `${originalBrowserEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToBrowserEntry, originalBrowserEntryContent); + fs.writeFileSync(pathToServerEntry, originalServerEntryContent); + } + }); + + it(`should work with universal configuration when only hot reload is enabled, and do hot reload for browser compiler when browser entry changed`, async () => { + const compiler = webpack(universalConfiguration); + const devServerOptions = { + port, + hot: true, + liveReload: false + }; + const pathToBrowserEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/browser.js" + ); + const originalBrowserEntryContent = fs.readFileSync(pathToBrowserEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const serverResponse = await page.goto( + `http://127.0.0.1:${port}/server.js`, + { + waitUntil: "networkidle0" + } + ); + + const serverResponseText = await serverResponse.text(); + + expect(serverResponseText).toContain("Hello from the server"); + expect(serverResponseText).not.toContain("WebsocketServer"); + + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + let text = message.text(); + + if (/Error: Aborted because/.test(text)) { + const splittedText = text.split("\n"); + + text = `${splittedText[0]}\n${splittedText[1]}\n `; + } + + consoleMessages.push(text); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToBrowserEntry, + `${originalBrowserEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToBrowserEntry, originalBrowserEntryContent); + } + }); + + it(`should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing browser and server entries`, async () => { + const compiler = webpack(universalConfiguration); + const devServerOptions = { + port, + hot: false, + liveReload: true + }; + const pathToBrowserEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/browser.js" + ); + const originalBrowserEntryContent = fs.readFileSync(pathToBrowserEntry); + const pathToServerEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/server.js" + ); + const originalServerEntryContent = fs.readFileSync(pathToServerEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const serverResponse = await page.goto( + `http://127.0.0.1:${port}/server.js`, + { + waitUntil: "networkidle0" + } + ); + + const serverResponseText = await serverResponse.text(); + + expect(serverResponseText).toContain("Hello from the server"); + expect(serverResponseText).not.toContain("WebsocketServer"); + + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToBrowserEntry, + `${originalBrowserEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToServerEntry, + `${originalServerEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToBrowserEntry, originalBrowserEntryContent); + fs.writeFileSync(pathToServerEntry, originalServerEntryContent); + } + }); + + it(`should work with universal configuration when only live reload is enabled, and do live reload for browser compiler when changing server and browser entries`, async () => { + const compiler = webpack(universalConfiguration); + const devServerOptions = { + port, + hot: false, + liveReload: true + }; + const pathToBrowserEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/browser.js" + ); + const originalBrowserEntryContent = fs.readFileSync(pathToBrowserEntry); + const pathToServerEntry = path.resolve( + __dirname, + "../fixtures/universal-compiler-config/server.js" + ); + const originalServerEntryContent = fs.readFileSync(pathToServerEntry); + + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const serverResponse = await page.goto( + `http://127.0.0.1:${port}/server.js`, + { + waitUntil: "networkidle0" + } + ); + + const serverResponseText = await serverResponse.text(); + + expect(serverResponseText).toContain("Hello from the server"); + expect(serverResponseText).not.toContain("WebsocketServer"); + + let pageErrors = []; + let consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToServerEntry, + `${originalServerEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + + pageErrors = []; + consoleMessages = []; + + await page.goto(`http://127.0.0.1:${port}/browser.html`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + pathToBrowserEntry, + `${originalBrowserEntryContent}// comment` + ); + + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + fs.writeFileSync(pathToBrowserEntry, originalBrowserEntryContent); + fs.writeFileSync(pathToServerEntry, originalServerEntryContent); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/on-listening.test.js b/packages/rspack-dev-server/tests/e2e/on-listening.test.js new file mode 100644 index 00000000000..66aa909e19c --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/on-listening.test.js @@ -0,0 +1,127 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["on-listening-option"]; + +describe("onListening option", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + let onListeningIsRunning = false; + + beforeEach(async () => { + compiler = webpack(config); + server = new Server( + { + onListening: devServer => { + if (!devServer) { + throw new Error("webpack-dev-server is not defined"); + } + + onListeningIsRunning = true; + + devServer.app.get("/listening/some/path", (_, response) => { + response.send("listening"); + }); + + devServer.app.post("/listening/some/path", (_, response) => { + response.send("listening POST"); + }); + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to /listening/some/path route", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/listening/some/path`, + { + waitUntil: "networkidle0" + } + ); + + expect(onListeningIsRunning).toBe(true); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle POST request to /listening/some/path route", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "POST" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/listening/some/path`, + { + waitUntil: "networkidle0" + } + ); + + expect(onListeningIsRunning).toBe(true); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/overlay.test.js b/packages/rspack-dev-server/tests/e2e/overlay.test.js new file mode 100644 index 00000000000..8b1ccc6adad --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/overlay.test.js @@ -0,0 +1,1991 @@ +"use strict"; + +const path = require("path"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const waitForExpect = require("wait-for-expect"); +const config = require("../fixtures/overlay-config/webpack.config"); +const trustedTypesConfig = require("../fixtures/overlay-config/trusted-types.webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").overlay; +require("../helpers/normalize"); + +class ErrorPlugin { + constructor(message, skipCounter) { + this.message = + message || "Error from compilation. Can't find 'test' module."; + this.skipCounter = skipCounter; + this.counter = 0; + } + + apply(compiler) { + compiler.hooks.thisCompilation.tap("errors-webpack-plugin", compilation => { + if ( + typeof this.skipCounter !== "undefined" && + this.counter !== this.skipCounter + ) { + this.counter += 1; + + return; + } + + compilation.errors.push(new Error(this.message)); + }); + } +} + +class WarningPlugin { + constructor(message, skipCounter) { + this.message = message || "Warning from compilation"; + this.skipCounter = skipCounter; + this.counter = 0; + } + + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + if ( + typeof this.skipCounter !== "undefined" && + this.counter !== this.skipCounter + ) { + this.counter += 1; + + return; + } + + compilation.warnings.push(new Error(this.message)); + } + ); + } +} + +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + +let prettier; +let prettierHTML; +let prettierCSS; + +describe("overlay", () => { + beforeAll(async () => { + // Due problems with ESM modules for Node.js@18 + // TODO replace it on import/require when Node.js@18 will be dropped + prettier = require("../../node_modules/prettier/standalone"); + prettierHTML = require("../../node_modules/prettier/plugins/html"); + prettierCSS = require("../../node_modules/prettier/plugins/postcss"); + }); + + it("should show a warning for initial compilation", async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show an error for initial compilation", async () => { + const compiler = webpack(config); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show a warning and error for initial compilation", async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + new WarningPlugin().apply(compiler); + new ErrorPlugin().apply(compiler); + new ErrorPlugin().apply(compiler); + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show an ansi formatted error for initial compilation", async () => { + const compiler = webpack(config); + + new ErrorPlugin( + "  18 | Render ansi formatted text" + ).apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show a warning and error for initial compilation and protects against xss", async () => { + const compiler = webpack(config); + + new WarningPlugin("strong").apply(compiler); + new ErrorPlugin("strong").apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show initially, then show on an error, then hide on fix", async () => { + const compiler = webpack(config); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + let pageHtml = await page.evaluate(() => document.body.outerHTML); + let overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html initial"); + + const pathToFile = path.resolve( + __dirname, + "../fixtures/overlay-config/foo.js" + ); + const originalCode = fs.readFileSync(pathToFile); + + fs.writeFileSync(pathToFile, "`;"); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + pageHtml = await page.evaluate(() => document.body.outerHTML); + + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html with error"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + + fs.writeFileSync(pathToFile, originalCode); + + await page.waitForSelector("#webpack-dev-server-client-overlay", { + hidden: true + }); + + pageHtml = await page.evaluate(() => document.body.outerHTML); + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html after fix error"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show initially, then show on an error, then show other error, then hide on fix", async () => { + const compiler = webpack(config); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + let pageHtml = await page.evaluate(() => document.body.outerHTML); + let overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html initial"); + + const pathToFile = path.resolve( + __dirname, + "../fixtures/overlay-config/foo.js" + ); + const originalCode = fs.readFileSync(pathToFile); + + fs.writeFileSync(pathToFile, "`;"); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + pageHtml = await page.evaluate(() => document.body.outerHTML); + + let overlayFrame = await overlayHandle.contentFrame(); + let overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html with error"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + + fs.writeFileSync(pathToFile, "`;a"); + + await page.waitForSelector("#webpack-dev-server-client-overlay", { + hidden: true + }); + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + pageHtml = await page.evaluate(() => document.body.outerHTML); + + overlayFrame = await overlayHandle.contentFrame(); + overlayHtml = await overlayFrame.evaluate(() => document.body.outerHTML); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html with other error"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + + fs.writeFileSync(pathToFile, originalCode); + + await page.waitForSelector("#webpack-dev-server-client-overlay", { + hidden: true + }); + + pageHtml = await page.evaluate(() => document.body.outerHTML); + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html after fix error"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show initially, then show on an error and allow to close", async () => { + const compiler = webpack(config); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + let pageHtml = await page.evaluate(() => document.body.outerHTML); + let overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html initial"); + + const pathToFile = path.resolve( + __dirname, + "../fixtures/overlay-config/foo.js" + ); + const originalCode = fs.readFileSync(pathToFile); + + fs.writeFileSync(pathToFile, "`;"); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + pageHtml = await page.evaluate(() => document.body.outerHTML); + + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html with error"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + + const frame = await page + .frames() + .find(item => item.name() === "webpack-dev-server-client-overlay"); + + const buttonHandle = await frame.$("button"); + + await buttonHandle.click(); + + await page.waitForSelector("#webpack-dev-server-client-overlay", { + hidden: true + }); + + pageHtml = await page.evaluate(() => document.body.outerHTML); + overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html after close"); + + fs.writeFileSync(pathToFile, originalCode); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should open editor when error with file info is clicked", async () => { + const mockLaunchEditorCb = jest.fn(); + jest.mock("launch-editor", () => mockLaunchEditorCb); + + const compiler = webpack(config); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const pathToFile = path.resolve( + __dirname, + "../fixtures/overlay-config/foo.js" + ); + const originalCode = fs.readFileSync(pathToFile); + + fs.writeFileSync(pathToFile, "`;"); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + const frame = page + .frames() + .find(item => item.name() === "webpack-dev-server-client-overlay"); + + const errorHandle = await frame.$("[data-can-open]"); + + await errorHandle.click(); + + await waitForExpect(() => { + expect(mockLaunchEditorCb).toHaveBeenCalledTimes(1); + }); + + fs.writeFileSync(pathToFile, originalCode); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should not show a warning when "client.overlay" is "false"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: false + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should not show a warning when "client.overlay.warnings" is "false"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + warnings: false + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show warning when it is filtered", async () => { + const compiler = webpack(config); + + new WarningPlugin("My special warning").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + warnings: error => { + // error is string in webpack 4 + const message = typeof error === "string" ? error : error.message; + return !message.includes("My special warning"); + } + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show warning when it is not filtered", async () => { + const compiler = webpack(config); + + new WarningPlugin("Unfiltered warning").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + warnings: () => true + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show a warning when "client.overlay" is "true"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: true + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show a warning when "client.overlay.warnings" is "true"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + warnings: true + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show a warning when "client.overlay.errors" is "true"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + errors: true + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should not show an error when "client.overlay" is "false"', async () => { + const compiler = webpack(config); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: false + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should not show an error when "client.overlay.errors" is "false"', async () => { + const compiler = webpack(config); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + errors: false + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show error when it is filtered", async () => { + const compiler = webpack(config); + + new ErrorPlugin("My special error").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + errors: error => { + // error is string in webpack 4 + const message = typeof error === "string" ? error : error.message; + + return !message.includes("My special error"); + } + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show error when it is not filtered", async () => { + const compiler = webpack(config); + + new ErrorPlugin("Unfiltered error").apply(compiler); + + const server = new Server( + { + port, + client: { + overlay: { + errors: () => true + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show an error when "client.overlay" is "true"', async () => { + const compiler = webpack(config); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: true + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + // it("should show overlay when Trusted Types are enabled", async () => { + // const compiler = webpack(trustedTypesConfig); + + // new ErrorPlugin().apply(compiler); + + // const devServerOptions = { + // port, + // client: { + // overlay: { + // trustedTypesPolicyName: "webpack#dev-overlay", + // }, + // }, + // }; + // const server = new Server(devServerOptions, compiler); + + // await server.start(); + + // const { page, browser } = await runBrowser(); + + // try { + // const consoleMessages = []; + + // page.on("console", (message) => { + // consoleMessages.push(message.text()); + // }); + + // await page.goto(`http://localhost:${port}/`, { + // waitUntil: "networkidle0", + // }); + + // // Delay for the overlay to appear + // await delay(1000); + + // const pageHtml = await page.evaluate(() => document.body.outerHTML); + // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + // const overlayFrame = await overlayHandle.contentFrame(); + // const overlayHtml = await overlayFrame.evaluate( + // () => document.body.outerHTML, + // ); + + // expect( + // consoleMessages.filter((item) => + // /requires 'TrustedHTML' assignment/.test(item), + // ), + // ).toHaveLength(0); + // expect( + // await prettier.format(pageHtml, { + // parser: "html", + // plugins: [prettierHTML, prettierCSS], + // }), + // ).toMatchSnapshot("page html"); + // expect( + // await prettier.format(overlayHtml, { + // parser: "html", + // plugins: [prettierHTML, prettierCSS], + // }), + // ).toMatchSnapshot("overlay html"); + // } catch (error) { + // throw error; + // } finally { + // await browser.close(); + // await server.stop(); + // } + // }); + + // it("should show overlay when Trusted Types are enabled and the \"require-trusted-types-for 'script'\" header was used", async () => { + // const compiler = webpack(trustedTypesConfig); + + // new ErrorPlugin().apply(compiler); + + // const devServerOptions = { + // port, + // headers: [ + // { + // key: "Content-Security-Policy", + // value: "require-trusted-types-for 'script'", + // }, + // ], + // client: { + // overlay: { + // trustedTypesPolicyName: "webpack#dev-overlay", + // }, + // }, + // }; + // const server = new Server(devServerOptions, compiler); + + // await server.start(); + + // const { page, browser } = await runBrowser(); + + // try { + // const consoleMessages = []; + + // page.on("console", (message) => { + // consoleMessages.push(message.text()); + // }); + + // await page.goto(`http://localhost:${port}/`, { + // waitUntil: "networkidle0", + // }); + + // // Delay for the overlay to appear + // await delay(1000); + + // const pageHtml = await page.evaluate(() => document.body.outerHTML); + // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + // const overlayFrame = await overlayHandle.contentFrame(); + // const overlayHtml = await overlayFrame.evaluate( + // () => document.body.outerHTML, + // ); + + // await page.goto(`http://localhost:${port}/`, { + // waitUntil: "networkidle0", + // }); + + // expect( + // consoleMessages.filter((item) => + // /requires 'TrustedHTML' assignment/.test(item), + // ), + // ).toHaveLength(0); + // expect( + // await prettier.format(pageHtml, { + // parser: "html", + // plugins: [prettierHTML, prettierCSS], + // }), + // ).toMatchSnapshot("page html"); + // expect( + // await prettier.format(overlayHtml, { + // parser: "html", + // plugins: [prettierHTML, prettierCSS], + // }), + // ).toMatchSnapshot("overlay html"); + // } catch (error) { + // throw error; + // } finally { + // await browser.close(); + // await server.stop(); + // } + // }); + + // it("should not show overlay when Trusted Types are enabled, but policy is not allowed", async () => { + // const compiler = webpack(trustedTypesConfig); + + // new ErrorPlugin().apply(compiler); + + // const devServerOptions = { + // port, + // client: { + // overlay: { + // trustedTypesPolicyName: "disallowed-policy", + // }, + // }, + // }; + // const server = new Server(devServerOptions, compiler); + + // await server.start(); + + // const { page, browser } = await runBrowser(); + + // try { + // await page.goto(`http://localhost:${port}/`, { + // waitUntil: "networkidle0", + // }); + + // // Delay for the overlay to appear + // await delay(1000); + + // const pageHtml = await page.evaluate(() => document.body.outerHTML); + // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + // expect(overlayHandle).toBe(null); + // expect( + // await prettier.format(pageHtml, { + // parser: "html", + // plugins: [prettierHTML, prettierCSS], + // }), + // ).toMatchSnapshot("page html"); + // } catch (error) { + // throw error; + // } finally { + // await browser.close(); + // await server.stop(); + // } + // }); + + it('should show an error when "client.overlay.errors" is "true"', async () => { + const compiler = webpack(config); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + errors: true + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show an error when "client.overlay.warnings" is "true"', async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + warnings: true + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show a warning and hide them after closing connection", async () => { + const compiler = webpack(config); + + new WarningPlugin().apply(compiler); + + const devServerOptions = { port }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message.text()); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + + await server.stop(); + + await new Promise(resolve => { + const interval = setInterval(() => { + if (consoleMessages.includes("[webpack-dev-server] Disconnected!")) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + const pageHtmlAfterClose = await page.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtmlAfterClose, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + }); + + it("should show an error after invalidation", async () => { + const compiler = webpack(config); + + new ErrorPlugin("Error from compilation", 1).apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await new Promise(resolve => { + server.middleware.invalidate(() => { + resolve(); + }); + }); + + await new Promise(resolve => { + server.middleware.waitUntilValid(() => { + resolve(); + }); + }); + + // Delay for the overlay to appear + await delay(1000); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show a warning after invalidation", async () => { + const compiler = webpack(config); + + new WarningPlugin("Warning from compilation", 1).apply(compiler); + + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await new Promise(resolve => { + server.middleware.invalidate(() => { + resolve(); + }); + }); + + await new Promise(resolve => { + server.middleware.waitUntilValid(() => { + resolve(); + }); + }); + + // Delay for the overlay to appear + await delay(1000); + + await page.waitForSelector("#webpack-dev-server-client-overlay"); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show error for uncaught runtime error", async () => { + const compiler = webpack(config); + + const server = new Server( + { + port + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await page.addScriptTag({ + content: `(function throwError() { + throw new Error('Injected error'); + })();` + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show filtered runtime error", async () => { + const compiler = webpack(config); + + const server = new Server( + { + port, + client: { + overlay: { + runtimeErrors: error => error && !/Injected/.test(error.message) + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await page.addScriptTag({ + content: `(function throwError() { + throw new Error('Injected error'); + })();` + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show error for uncaught promise rejection", async () => { + const compiler = webpack(config); + + const server = new Server( + { + port + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await page.addScriptTag({ + content: `(function throwError() { + setTimeout(function () { + Promise.reject(new Error('Async error')); + }, 0); + })();` + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show filtered promise rejection", async () => { + const compiler = webpack(config); + + const server = new Server( + { + port, + client: { + overlay: { + runtimeErrors: error => !/Injected/.test(error.message) + } + } + }, + compiler + ); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + await page.addScriptTag({ + content: `(function throwError() { + setTimeout(function () { + Promise.reject(new Error('Injected async error')); + }, 0); + })();` + }); + + // Delay for the overlay to appear + await delay(1000); + + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + + expect(overlayHandle).toBe(null); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should show overlay when "Content-Security-Policy" is "default-src \'self\'" was used', async () => { + const compiler = webpack({ ...config, devtool: false }); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + headers: [ + { + key: "Content-Security-Policy", + value: "default-src 'self'" + } + ] + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message.text()); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/fixtures/mime-types-config/file.custom b/packages/rspack-dev-server/tests/fixtures/mime-types-config/file.custom new file mode 100644 index 00000000000..9daeafb9864 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/mime-types-config/file.custom @@ -0,0 +1 @@ +test diff --git a/packages/rspack-dev-server/tests/fixtures/mime-types-config/foo.js b/packages/rspack-dev-server/tests/fixtures/mime-types-config/foo.js new file mode 100644 index 00000000000..739d9bd6383 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/mime-types-config/foo.js @@ -0,0 +1,5 @@ +"use strict"; + +require("./file.custom"); + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/mime-types-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/mime-types-config/webpack.config.js new file mode 100644 index 00000000000..b96e4e6aec7 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/mime-types-config/webpack.config.js @@ -0,0 +1,30 @@ +"use strict"; + +const moduleRuleForCustom = { + test: /\.custom$/, + type: "asset/resource", + generator: { + filename: "[name][ext]" + } +}; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "warn" + }, + module: { + rules: [ + { + ...moduleRuleForCustom + } + ] + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry1.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry1.js new file mode 100644 index 00000000000..dbd0f49a83e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry1.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = "entry1"; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry2.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry2.js new file mode 100644 index 00000000000..74f9bc577fd --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/entry2.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = "entry2"; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.config.js new file mode 100644 index 00000000000..d513196fbb5 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.config.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = { + mode: "development", + target: "node", + stats: "none", + context: __dirname, + entry: ["./entry1.js", "./entry2.js"], + output: { + path: "/", + libraryTarget: "umd" + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.multi.config.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.multi.config.js new file mode 100644 index 00000000000..de92d76ab21 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.multi.config.js @@ -0,0 +1,18 @@ +"use strict"; + +module.exports = [ + { + mode: "development", + target: "node", + context: __dirname, + stats: "none", + entry: ["./entry1.js", "./entry2.js"], + output: { + path: "/", + libraryTarget: "umd" + }, + infrastructureLogging: { + level: "warn" + } + } +]; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.object-entry.config.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.object-entry.config.js new file mode 100644 index 00000000000..7bbc9e8daa6 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.object-entry.config.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + mode: "development", + target: "node", + stats: "none", + context: __dirname, + entry: { + foo: "./entry1.js", + main: ["./entry1.js", "./entry2.js"] + }, + output: { + path: "/", + libraryTarget: "umd" + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.plugin.js b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.plugin.js new file mode 100644 index 00000000000..f6ab0edfcae --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/module-federation-config/webpack.plugin.js @@ -0,0 +1,25 @@ +"use strict"; + +const ModuleFederationPlugin = + require("@rspack/core").container.ModuleFederationPlugin; + +module.exports = { + mode: "development", + target: "node", + stats: "none", + context: __dirname, + entry: ["./entry1.js"], + plugins: [ + new ModuleFederationPlugin({ + name: "app1", + library: { type: "var", name: "app1" }, + filename: "remoteEntry.js", + exposes: { + "./entry1": "./entry1" + } + }) + ], + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js new file mode 100644 index 00000000000..11e40ee4d5d --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js @@ -0,0 +1,24 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = [ + { + target: "web", + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] + } +]; diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/one.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/one.js new file mode 100644 index 00000000000..21aaff9181a --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/one.js @@ -0,0 +1,4 @@ +"use strict"; + +console.log("one"); +// comment diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/two.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/two.js new file mode 100644 index 00000000000..83bf26e4bb8 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/two.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("two"); diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/webpack.config.js new file mode 100644 index 00000000000..0d3aa41b8e8 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/multi-compiler-two-configurations/webpack.config.js @@ -0,0 +1,44 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = [ + { + target: "web", + name: "one", + mode: "development", + context: __dirname, + entry: "./one.js", + stats: "none", + output: { + path: "/", + filename: "one-[name].js" + }, + plugins: [new HTMLGeneratorPlugin()], + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + } + }, + { + target: "web", + name: "two", + mode: "development", + context: __dirname, + entry: "./two.js", + stats: "none", + output: { + path: "/", + filename: "two-[name].js" + }, + plugins: [new HTMLGeneratorPlugin()], + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + } + } +]; diff --git a/packages/rspack-dev-server/tests/fixtures/overlay-config/foo.js b/packages/rspack-dev-server/tests/fixtures/overlay-config/foo.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/overlay-config/trusted-types.webpack.config.js b/packages/rspack-dev-server/tests/fixtures/overlay-config/trusted-types.webpack.config.js new file mode 100644 index 00000000000..5cfa4ecfef2 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/overlay-config/trusted-types.webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/trusted-types-html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/", + trustedTypes: { policyName: "webpack" } + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/overlay-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/overlay-config/webpack.config.js new file mode 100644 index 00000000000..16bae608229 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/overlay-config/webpack.config.js @@ -0,0 +1,20 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css deleted file mode 100644 index 585e8f34510..00000000000 --- a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css +++ /dev/null @@ -1 +0,0 @@ -body { background-color: rgb(0, 0, 255); } \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js index 58a634a77a4..f85af87b103 100644 --- a/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js +++ b/packages/rspack-dev-server/tests/fixtures/reload-config/webpack.config.js @@ -10,16 +10,13 @@ module.exports = { output: { path: "/" }, - experiments: { - css: true - }, module: { - // rules: [ - // { - // test: /\.css$/, - // use: [{ loader: "style-loader" }, { loader: "css-loader" }], - // }, - // ], + rules: [ + { + test: /\.css$/, + use: [{ loader: "style-loader" }, { loader: "css-loader" }] + } + ] }, infrastructureLogging: { level: "info", diff --git a/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/browser.js b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/browser.js new file mode 100644 index 00000000000..ddcebf29c76 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/browser.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hello from the browser"); diff --git a/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/server.js b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/server.js new file mode 100644 index 00000000000..3dfb585f6d8 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/server.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hello from the server"); diff --git a/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/webpack.config.js new file mode 100644 index 00000000000..1ddc7f280b5 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/universal-compiler-config/webpack.config.js @@ -0,0 +1,43 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = [ + { + name: "browser", + mode: "development", + context: __dirname, + stats: "none", + entry: "./browser.js", + output: { + path: "/", + filename: "browser.js" + }, + plugins: [new HTMLGeneratorPlugin()], + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + } + }, + { + name: "server", + mode: "development", + context: __dirname, + target: "node", + stats: "none", + entry: "./server.js", + output: { + path: "/", + filename: "server.js" + }, + plugins: [new HTMLGeneratorPlugin()], + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + } + } +]; diff --git a/packages/rspack-dev-server/tests/helpers/html-generator-plugin.js b/packages/rspack-dev-server/tests/helpers/html-generator-plugin.js index 2db3b6bddcf..e8da3b941ac 100644 --- a/packages/rspack-dev-server/tests/helpers/html-generator-plugin.js +++ b/packages/rspack-dev-server/tests/helpers/html-generator-plugin.js @@ -65,7 +65,7 @@ module.exports = class HTMLGeneratorPlugin { for (const asset of assets) { const assetName = asset.name; - if (assetName !== "main.js") { + if (assetName !== "main.js" && assetName.endsWith(".js")) { const assetSource = new RawSource( HTMLContentForAssets(assetName) ); diff --git a/packages/rspack-dev-server/tests/helpers/normalize.js b/packages/rspack-dev-server/tests/helpers/normalize.js new file mode 100644 index 00000000000..092911a4123 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/normalize.js @@ -0,0 +1,49 @@ +const path = require("path"); + +const CURRENT_CWD = process.cwd(); +const ROOT = path.resolve(__dirname, "../../../../"); + +const quoteMeta = str => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&"); +const cwdRegExp = new RegExp( + `${quoteMeta(CURRENT_CWD)}((?:\\\\)?(?:[a-zA-Z.\\-_]+\\\\)*)`, + "g" +); +const escapedCwd = JSON.stringify(CURRENT_CWD).slice(1, -1); +const escapedCwdRegExp = new RegExp( + `${quoteMeta(escapedCwd)}((?:\\\\\\\\)?(?:[a-zA-Z.\\-_]+\\\\\\\\)*)`, + "g" +); +const normalize = str => { + let normalizedStr = str; + if (CURRENT_CWD.startsWith("/")) { + normalizedStr = normalizedStr.replace( + new RegExp(quoteMeta(CURRENT_CWD), "g"), + "" + ); + } else { + normalizedStr = normalizedStr.replace( + cwdRegExp, + (_, g) => `${g.replace(/\\/g, "/")}` + ); + normalizedStr = normalizedStr.replace( + escapedCwdRegExp, + (_, g) => `${g.replace(/\\\\/g, "/")}` + ); + } + normalizedStr = normalizedStr.split(ROOT).join(""); + normalizedStr = normalizedStr.replace(/:\d+:\d+/g, "::"); + normalizedStr = normalizedStr.replace( + /@@ -\d+,\d+ \+\d+,\d+ @@/g, + "@@ ... @@" + ); + return normalizedStr; +}; + +expect.addSnapshotSerializer({ + test(value) { + return typeof value === "string"; + }, + print(received) { + return normalize(received); + } +}); diff --git a/packages/rspack-dev-server/tests/helpers/trusted-types-html-generator-plugin.js b/packages/rspack-dev-server/tests/helpers/trusted-types-html-generator-plugin.js new file mode 100644 index 00000000000..ee647d3d4ef --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/trusted-types-html-generator-plugin.js @@ -0,0 +1,82 @@ +"use strict"; + +const HTMLContentForIndex = ` + + + + + + webpack-dev-server + + +

webpack-dev-server is running...

+ + + +`; + +const HTMLContentForTest = ` + + + + + + test + + +

Created via HTMLGeneratorPlugin

+ + +`; + +module.exports = class HTMLGeneratorPlugin { + // eslint-disable-next-line class-methods-use-this + apply(compiler) { + const pluginName = "html-generator-plugin"; + + compiler.hooks.thisCompilation.tap(pluginName, compilation => { + if (compiler.webpack) { + const { RawSource } = compiler.webpack.sources; + + compilation.hooks.processAssets.tap( + { + name: pluginName, + stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL + }, + () => { + const indexSource = new RawSource(HTMLContentForIndex); + const testSource = new RawSource(HTMLContentForTest); + + compilation.emitAsset("index.html", indexSource); + compilation.emitAsset("test.html", testSource); + } + ); + } else { + compilation.hooks.additionalAssets.tap(pluginName, () => { + compilation.emitAsset("index.html", { + source() { + return HTMLContentForIndex; + }, + size() { + return HTMLContentForIndex.length; + } + }); + compilation.emitAsset("test.html", { + source() { + return HTMLContentForTest; + }, + size() { + return HTMLContentForTest.length; + } + }); + }); + } + }); + } +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0545c1a9ee3..cd471959f1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -534,6 +534,9 @@ importers: specifier: ^8.16.0 version: 8.17.1 devDependencies: + '@jest/test-sequencer': + specifier: ^29.7.0 + version: 29.7.0 '@rspack/core': specifier: workspace:* version: link:../rspack @@ -555,6 +558,9 @@ importers: '@types/ws': specifier: 8.5.10 version: 8.5.10 + css-loader: + specifier: ^6.11.0 + version: 6.11.0(@rspack/core@packages+rspack)(webpack@5.94.0(webpack-cli@5.1.4(webpack@5.94.0))) graceful-fs: specifier: 4.2.10 version: 4.2.10(patch_hash=ivtm2a2cfr5pomcfbedhmr5v2q) @@ -564,12 +570,21 @@ importers: jest-serializer-path: specifier: ^0.1.15 version: 0.1.15 + prettier: + specifier: 3.2.5 + version: 3.2.5 puppeteer: specifier: ^23.2.2 version: 23.3.0(typescript@5.0.2) + require-from-string: + specifier: ^2.0.2 + version: 2.0.2 sockjs-client: specifier: ^1.6.1 version: 1.6.1 + style-loader: + specifier: ^3.3.3 + version: 3.3.4(webpack@5.94.0(webpack-cli@5.1.4(webpack@5.94.0))) supertest: specifier: ^6.1.3 version: 6.3.4 @@ -579,6 +594,9 @@ importers: typescript: specifier: 5.0.2 version: 5.0.2 + wait-for-expect: + specifier: ^3.0.2 + version: 3.0.2 packages/rspack-test-tools: dependencies: @@ -9438,6 +9456,9 @@ packages: wabt@1.0.0-nightly.20180421: resolution: {integrity: sha512-bsu9zk672KACjoabONcAS94IS20prRm05IbiIUGfa8eBpRLjWZv8ugocdinV/ONh0mFMfXrVWkvF1/BNtwIfUw==} + wait-for-expect@3.0.2: + resolution: {integrity: sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==} + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -20087,6 +20108,8 @@ snapshots: wabt@1.0.0-nightly.20180421: {} + wait-for-expect@3.0.2: {} + walker@1.0.8: dependencies: makeerror: 1.0.12 From b1bf1531a710778dbb76caf4ab3d3d390868bb07 Mon Sep 17 00:00:00 2001 From: noshower <13777850432@163.com> Date: Wed, 11 Sep 2024 18:30:20 +0800 Subject: [PATCH 27/51] fix: remove LightningCssMinimizerRspackPlugin exclude option default value (#7811) fix: remove LightningCssMinimizerRspackPlugin exclude option default value --- .../tests/__snapshots__/StatsAPI.test.js.snap | 14 +++++++------- .../rspack-test-tools/tests/statsAPICases/basic.js | 2 +- .../tests/statsAPICases/chunks.js | 4 ++-- .../LightningCssMiminizerRspackPlugin.ts | 6 +----- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/rspack-test-tools/tests/__snapshots__/StatsAPI.test.js.snap b/packages/rspack-test-tools/tests/__snapshots__/StatsAPI.test.js.snap index 94d5499bd92..ebee1810982 100644 --- a/packages/rspack-test-tools/tests/__snapshots__/StatsAPI.test.js.snap +++ b/packages/rspack-test-tools/tests/__snapshots__/StatsAPI.test.js.snap @@ -52,7 +52,7 @@ Object { "main.js", ], "filteredModules": undefined, - "hash": "333e59d94047c9b4ec6c", + "hash": "ef6849a04b135f983911", "id": "909", "idHints": Array [], "initial": true, @@ -176,7 +176,7 @@ Object { "errorsCount": 0, "filteredAssets": undefined, "filteredModules": undefined, - "hash": "a156b269cd7b4340da9d", + "hash": "d27b1f8bbdf2e13f1b91", "modules": Array [ Object { "assets": Array [], @@ -324,7 +324,7 @@ Object { "main.js", ], "filteredModules": undefined, - "hash": "a2024ddd80c1dac51782", + "hash": "eceae76a4802a119b192", "id": "909", "idHints": Array [], "initial": true, @@ -691,7 +691,7 @@ Object { "errorsCount": 0, "filteredAssets": undefined, "filteredModules": undefined, - "hash": "959ba20161293349edf3", + "hash": "b2ef257387e69373f7b0", "modules": Array [ Object { "assets": Array [], @@ -1458,7 +1458,7 @@ Object { "files": Array [ "main.js", ], - "hash": "333e59d94047c9b4ec6c", + "hash": "ef6849a04b135f983911", "id": "909", "idHints": Array [], "initial": true, @@ -1714,7 +1714,7 @@ Object { "main.js", ], "filteredModules": undefined, - "hash": "add6ca394a084131cdb7", + "hash": "770328aa6786a7bd40f1", "id": "909", "idHints": Array [], "initial": true, @@ -2063,7 +2063,7 @@ exports.c = require(\\"./c?c=3\\"); "errorsCount": 0, "filteredAssets": undefined, "filteredModules": undefined, - "hash": "0e205cc26142ad8eade1", + "hash": "e5c3f8bfb19708a3a81e", "modules": Array [ Object { "assets": Array [], diff --git a/packages/rspack-test-tools/tests/statsAPICases/basic.js b/packages/rspack-test-tools/tests/statsAPICases/basic.js index b602a395f1d..d3366e4f938 100644 --- a/packages/rspack-test-tools/tests/statsAPICases/basic.js +++ b/packages/rspack-test-tools/tests/statsAPICases/basic.js @@ -39,7 +39,7 @@ module.exports = { entry ./fixtures/a cjs self exports reference self [585] ./fixtures/a.js - Rspack compiled successfully (a156b269cd7b4340da9d)" + Rspack compiled successfully (d27b1f8bbdf2e13f1b91)" `); } }; diff --git a/packages/rspack-test-tools/tests/statsAPICases/chunks.js b/packages/rspack-test-tools/tests/statsAPICases/chunks.js index 6c95a3bbe1b..0a6d5d1a139 100644 --- a/packages/rspack-test-tools/tests/statsAPICases/chunks.js +++ b/packages/rspack-test-tools/tests/statsAPICases/chunks.js @@ -27,7 +27,7 @@ module.exports = { "chunkB.js", ], "filteredModules": undefined, - "hash": "9aa837bfaefd1fd4ec18", + "hash": "bf7629c34eadb1d16318", "id": "250", "idHints": Array [], "initial": false, @@ -144,7 +144,7 @@ module.exports = { "main.js", ], "filteredModules": undefined, - "hash": "ed56b5e4b83c81c60edf", + "hash": "c0a99515ebfa264f9f23", "id": "909", "idHints": Array [], "initial": true, diff --git a/packages/rspack/src/builtin-plugin/LightningCssMiminizerRspackPlugin.ts b/packages/rspack/src/builtin-plugin/LightningCssMiminizerRspackPlugin.ts index 7416d0b8391..5305ea02efb 100644 --- a/packages/rspack/src/builtin-plugin/LightningCssMiminizerRspackPlugin.ts +++ b/packages/rspack/src/builtin-plugin/LightningCssMiminizerRspackPlugin.ts @@ -47,11 +47,7 @@ export const LightningCssMinimizerRspackPlugin = create( errorRecovery: options?.minimizerOptions?.errorRecovery ?? true, unusedSymbols: options?.minimizerOptions?.unusedSymbols ?? [], include: include ? toFeatures(include) : undefined, - exclude: exclude - ? toFeatures(exclude) - : // exclude all features, avoid downgrade css syntax when minimize - // 1048575 = Features.Empty | Features.Nesting | ... | Features.LogicalProperties - 1048575, + exclude: exclude ? toFeatures(exclude) : undefined, targets: typeof targets === "string" ? [targets] : targets, draft: draft ? { customMedia: draft.customMedia ?? false } : undefined, nonStandard: nonStandard From f6f179842cb490f59e8786f588eed66533fff6f1 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Thu, 12 Sep 2024 11:33:23 +0800 Subject: [PATCH 28/51] fix: trusted type with devtool eval (#7866) --- .../src/dependency/dependency_type.rs | 3 + .../src/eval_dev_tool_module_plugin.rs | 32 +- .../src/eval_source_map_dev_tool_plugin.rs | 32 +- .../worker/create_script_url_dependency.rs | 78 ++++ .../src/dependency/worker/mod.rs | 2 + .../src/parser_plugin/worker_plugin.rs | 12 +- .../src/runtime_module/create_script.rs | 46 +++ .../src/runtime_module/mod.rs | 2 + .../src/runtime_plugin.rs | 17 +- .../overlay.test.js.snap.webpack5 | 242 ++++++++++++ .../tests/e2e/overlay.test.js | 366 +++++++++--------- .../trusted-types/devtool-eval/test.filter.js | 1 - .../trusted-types/web-worker/test.filter.js | 7 - 13 files changed, 635 insertions(+), 205 deletions(-) create mode 100644 crates/rspack_plugin_javascript/src/dependency/worker/create_script_url_dependency.rs create mode 100644 crates/rspack_plugin_runtime/src/runtime_module/create_script.rs delete mode 100644 tests/webpack-test/configCases/trusted-types/devtool-eval/test.filter.js delete mode 100644 tests/webpack-test/configCases/trusted-types/web-worker/test.filter.js diff --git a/crates/rspack_core/src/dependency/dependency_type.rs b/crates/rspack_core/src/dependency/dependency_type.rs index f9cf8b42c7f..0dc2d29591b 100644 --- a/crates/rspack_core/src/dependency/dependency_type.rs +++ b/crates/rspack_core/src/dependency/dependency_type.rs @@ -37,6 +37,8 @@ pub enum DependencyType { NewUrl, // new Worker() NewWorker, + // create script url + CreateScriptUrl, // import.meta.webpackHot.accept ImportMetaHotAccept, // import.meta.webpackHot.decline @@ -118,6 +120,7 @@ impl DependencyType { DependencyType::CjsSelfReference => "cjs self exports reference", DependencyType::NewUrl => "new URL()", DependencyType::NewWorker => "new Worker()", + DependencyType::CreateScriptUrl => "create script url", DependencyType::ImportMetaHotAccept => "import.meta.webpackHot.accept", DependencyType::ImportMetaHotDecline => "import.meta.webpackHot.decline", DependencyType::ModuleHotAccept => "module.hot.accept", diff --git a/crates/rspack_plugin_devtool/src/eval_dev_tool_module_plugin.rs b/crates/rspack_plugin_devtool/src/eval_dev_tool_module_plugin.rs index 315b6d9ef7a..1f2877ba710 100644 --- a/crates/rspack_plugin_devtool/src/eval_dev_tool_module_plugin.rs +++ b/crates/rspack_plugin_devtool/src/eval_dev_tool_module_plugin.rs @@ -9,6 +9,7 @@ use rspack_core::{ ApplyContext, BoxModule, ChunkInitFragments, ChunkUkey, Compilation, CompilationParams, CompilerCompilation, CompilerOptions, Plugin, PluginContext, }; +use rspack_core::{CompilationAdditionalTreeRuntimeRequirements, RuntimeGlobals}; use rspack_error::Result; use rspack_hash::RspackHash; use rspack_hook::{plugin, plugin_hook}; @@ -83,6 +84,7 @@ async fn eval_devtool_plugin_compilation( hooks .inline_in_runtime_bailout .tap(eval_devtool_plugin_inline_in_runtime_bailout::new(self)); + Ok(()) } @@ -135,11 +137,16 @@ fn eval_devtool_plugin_render_module_content( .trim_start_matches('/') ) ); - // TODO: Implement support for the trustedTypes option. - // This will depend on the additionalModuleRuntimeRequirements hook. + + let module_content = + simd_json::to_string(&format!("{source}{footer}")).expect("failed to parse string"); RawSource::from(format!( "eval({});", - simd_json::to_string(&format!("{source}{footer}")).expect("failed to parse string") + if compilation.options.output.trusted_types.is_some() { + format!("{}({})", RuntimeGlobals::CREATE_SCRIPT, module_content) + } else { + module_content + } )) .boxed() }; @@ -183,10 +190,29 @@ impl Plugin for EvalDevToolModulePlugin { .compiler_hooks .compilation .tap(eval_devtool_plugin_compilation::new(self)); + ctx + .context + .compilation_hooks + .additional_tree_runtime_requirements + .tap(eval_devtool_plugin_additional_tree_runtime_requirements::new(self)); Ok(()) } } +#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for EvalDevToolModulePlugin)] +async fn eval_devtool_plugin_additional_tree_runtime_requirements( + &self, + compilation: &mut Compilation, + _chunk_ukey: &ChunkUkey, + runtime_requirements: &mut RuntimeGlobals, +) -> Result<()> { + if compilation.options.output.trusted_types.is_some() { + runtime_requirements.insert(RuntimeGlobals::CREATE_SCRIPT); + } + + Ok(()) +} + // https://tc39.es/ecma262/#sec-encode // UNESCAPED is combined by ALWAYS_UNESCAPED and ";/?:@&=+$,#" static UNESCAPED: LazyLock> = LazyLock::new(|| { diff --git a/crates/rspack_plugin_devtool/src/eval_source_map_dev_tool_plugin.rs b/crates/rspack_plugin_devtool/src/eval_source_map_dev_tool_plugin.rs index 51f9b3db6bd..6667e9cf665 100644 --- a/crates/rspack_plugin_devtool/src/eval_source_map_dev_tool_plugin.rs +++ b/crates/rspack_plugin_devtool/src/eval_source_map_dev_tool_plugin.rs @@ -5,8 +5,9 @@ use derivative::Derivative; use futures::future::join_all; use rspack_core::{ rspack_sources::{BoxSource, MapOptions, RawSource, Source, SourceExt}, - ApplyContext, BoxModule, ChunkInitFragments, ChunkUkey, Compilation, CompilationParams, - CompilerCompilation, CompilerOptions, ModuleIdentifier, Plugin, PluginContext, + ApplyContext, BoxModule, ChunkInitFragments, ChunkUkey, Compilation, + CompilationAdditionalTreeRuntimeRequirements, CompilationParams, CompilerCompilation, + CompilerOptions, ModuleIdentifier, Plugin, PluginContext, RuntimeGlobals, }; use rspack_error::Result; use rspack_hash::RspackHash; @@ -173,9 +174,15 @@ fn eval_source_map_devtool_plugin_render_module_content( let base64 = rspack_base64::encode_to_string(&map_buffer); let footer = format!("\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{base64}"); + let module_content = + simd_json::to_string(&format!("{source}{footer}")).expect("should convert to string"); RawSource::from(format!( "eval({});", - simd_json::to_string(&format!("{source}{footer}")).expect("should convert to string") + if compilation.options.output.trusted_types.is_some() { + format!("{}({})", RuntimeGlobals::CREATE_SCRIPT, module_content) + } else { + module_content + } )) .boxed() }; @@ -205,6 +212,20 @@ fn eval_source_map_devtool_plugin_inline_in_runtime_bailout( Ok(Some("the eval-source-map devtool is used.".to_string())) } +#[plugin_hook(CompilationAdditionalTreeRuntimeRequirements for EvalSourceMapDevToolPlugin)] +async fn eval_source_map_devtool_plugin_additional_tree_runtime_requirements( + &self, + compilation: &mut Compilation, + _chunk_ukey: &ChunkUkey, + runtime_requirements: &mut RuntimeGlobals, +) -> Result<()> { + if compilation.options.output.trusted_types.is_some() { + runtime_requirements.insert(RuntimeGlobals::CREATE_SCRIPT); + } + + Ok(()) +} + #[async_trait::async_trait] impl Plugin for EvalSourceMapDevToolPlugin { fn name(&self) -> &'static str { @@ -221,6 +242,11 @@ impl Plugin for EvalSourceMapDevToolPlugin { .compiler_hooks .compilation .tap(eval_source_map_devtool_plugin_compilation::new(self)); + ctx + .context + .compilation_hooks + .additional_tree_runtime_requirements + .tap(eval_source_map_devtool_plugin_additional_tree_runtime_requirements::new(self)); Ok(()) } } diff --git a/crates/rspack_plugin_javascript/src/dependency/worker/create_script_url_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/worker/create_script_url_dependency.rs new file mode 100644 index 00000000000..ad96495985f --- /dev/null +++ b/crates/rspack_plugin_javascript/src/dependency/worker/create_script_url_dependency.rs @@ -0,0 +1,78 @@ +use rspack_core::{ + AsContextDependency, AsModuleDependency, Compilation, Dependency, DependencyCategory, + DependencyId, DependencyTemplate, DependencyType, RealDependencyLocation, RuntimeGlobals, + RuntimeSpec, TemplateContext, TemplateReplaceSource, +}; + +#[derive(Debug, Clone)] +pub struct CreateScriptUrlDependency { + id: DependencyId, + range: RealDependencyLocation, + range_path: (u32, u32), +} + +impl CreateScriptUrlDependency { + pub fn new(range: RealDependencyLocation, range_path: (u32, u32)) -> Self { + Self { + id: DependencyId::new(), + range, + range_path, + } + } +} + +impl Dependency for CreateScriptUrlDependency { + fn id(&self) -> &DependencyId { + &self.id + } + + fn category(&self) -> &DependencyCategory { + &DependencyCategory::Worker + } + + fn dependency_type(&self) -> &DependencyType { + &DependencyType::CreateScriptUrl + } + + fn range(&self) -> Option<&RealDependencyLocation> { + Some(&self.range) + } + + fn could_affect_referencing_module(&self) -> rspack_core::AffectType { + rspack_core::AffectType::False + } +} + +impl DependencyTemplate for CreateScriptUrlDependency { + fn apply( + &self, + source: &mut TemplateReplaceSource, + code_generatable_context: &mut TemplateContext, + ) { + code_generatable_context + .runtime_requirements + .insert(RuntimeGlobals::CREATE_SCRIPT_URL); + + source.insert( + self.range_path.0, + format!("{}(", RuntimeGlobals::CREATE_SCRIPT_URL).as_str(), + None, + ); + source.insert(self.range_path.1, ")", None); + } + + fn dependency_id(&self) -> Option { + Some(self.id) + } + + fn update_hash( + &self, + _hasher: &mut dyn std::hash::Hasher, + _compilation: &Compilation, + _runtime: Option<&RuntimeSpec>, + ) { + } +} + +impl AsModuleDependency for CreateScriptUrlDependency {} +impl AsContextDependency for CreateScriptUrlDependency {} diff --git a/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs b/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs index f1b2a2bddd1..6763e61a220 100644 --- a/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/worker/mod.rs @@ -1,3 +1,5 @@ +mod create_script_url_dependency; +pub use create_script_url_dependency::CreateScriptUrlDependency; use rspack_core::{ get_chunk_from_ukey, AsContextDependency, Compilation, Dependency, DependencyCategory, DependencyId, DependencyTemplate, DependencyType, ExtendedReferencedExport, ModuleDependency, diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs index 9e4aa9dd958..4bc99daff2b 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/worker_plugin.rs @@ -21,7 +21,7 @@ use super::{ JavascriptParserPlugin, }; use crate::{ - dependency::WorkerDependency, + dependency::{CreateScriptUrlDependency, WorkerDependency}, utils::object_properties::get_literal_str_by_obj_prop, visitors::{JavascriptParser, TagInfoData}, webpack_comment::try_extract_webpack_magic_comment, @@ -118,6 +118,16 @@ fn add_dependencies( }))); parser.blocks.push(Box::new(block)); + + if parser.compiler_options.output.trusted_types.is_some() { + parser + .dependencies + .push(Box::new(CreateScriptUrlDependency::new( + span.into(), + parsed_path.range, + ))); + } + if let Some(range) = range { parser .presentational_dependencies diff --git a/crates/rspack_plugin_runtime/src/runtime_module/create_script.rs b/crates/rspack_plugin_runtime/src/runtime_module/create_script.rs new file mode 100644 index 00000000000..90c92c52733 --- /dev/null +++ b/crates/rspack_plugin_runtime/src/runtime_module/create_script.rs @@ -0,0 +1,46 @@ +use rspack_collections::Identifier; +use rspack_core::{ + impl_runtime_module, + rspack_sources::{BoxSource, RawSource, SourceExt}, + Compilation, RuntimeGlobals, RuntimeModule, +}; + +#[impl_runtime_module] +#[derive(Debug)] +pub struct CreateScriptRuntimeModule { + id: Identifier, +} + +impl Default for CreateScriptRuntimeModule { + fn default() -> Self { + Self::with_default(Identifier::from("webpack/runtime/create_script")) + } +} + +impl RuntimeModule for CreateScriptRuntimeModule { + fn name(&self) -> Identifier { + self.id + } + + fn generate(&self, compilation: &Compilation) -> rspack_error::Result { + Ok( + RawSource::from(format!( + r#" + {} = function(script){{ + return {}; + }}; + "#, + RuntimeGlobals::CREATE_SCRIPT, + if compilation.options.output.trusted_types.is_some() { + format!( + "{}().createScript(script)", + RuntimeGlobals::GET_TRUSTED_TYPES_POLICY + ) + } else { + "script".to_string() + } + )) + .boxed(), + ) + } +} diff --git a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs index 8d611b244f6..662b6e8052f 100644 --- a/crates/rspack_plugin_runtime/src/runtime_module/mod.rs +++ b/crates/rspack_plugin_runtime/src/runtime_module/mod.rs @@ -8,6 +8,7 @@ mod chunk_prefetch_trigger; mod chunk_preload_trigger; mod compat_get_default_export; mod create_fake_namespace_object; +mod create_script; mod create_script_url; mod define_property_getters; mod ensure_chunk; @@ -49,6 +50,7 @@ pub use chunk_prefetch_trigger::ChunkPrefetchTriggerRuntimeModule; pub use chunk_preload_trigger::ChunkPreloadTriggerRuntimeModule; pub use compat_get_default_export::CompatGetDefaultExportRuntimeModule; pub use create_fake_namespace_object::CreateFakeNamespaceObjectRuntimeModule; +pub use create_script::CreateScriptRuntimeModule; pub use create_script_url::CreateScriptUrlRuntimeModule; pub use define_property_getters::DefinePropertyGettersRuntimeModule; pub use ensure_chunk::EnsureChunkRuntimeModule; diff --git a/crates/rspack_plugin_runtime/src/runtime_plugin.rs b/crates/rspack_plugin_runtime/src/runtime_plugin.rs index 3a4bce9dead..e2c4441a6aa 100644 --- a/crates/rspack_plugin_runtime/src/runtime_plugin.rs +++ b/crates/rspack_plugin_runtime/src/runtime_plugin.rs @@ -18,13 +18,13 @@ use crate::runtime_module::{ chunk_has_css, is_enabled_for_chunk, AsyncRuntimeModule, AutoPublicPathRuntimeModule, BaseUriRuntimeModule, ChunkNameRuntimeModule, ChunkPrefetchPreloadFunctionRuntimeModule, CompatGetDefaultExportRuntimeModule, CreateFakeNamespaceObjectRuntimeModule, - CreateScriptUrlRuntimeModule, DefinePropertyGettersRuntimeModule, EnsureChunkRuntimeModule, - GetChunkFilenameRuntimeModule, GetChunkUpdateFilenameRuntimeModule, GetFullHashRuntimeModule, - GetMainFilenameRuntimeModule, GetTrustedTypesPolicyRuntimeModule, GlobalRuntimeModule, - HarmonyModuleDecoratorRuntimeModule, HasOwnPropertyRuntimeModule, LoadScriptRuntimeModule, - MakeNamespaceObjectRuntimeModule, NodeModuleDecoratorRuntimeModule, NonceRuntimeModule, - OnChunkLoadedRuntimeModule, PublicPathRuntimeModule, RelativeUrlRuntimeModule, - RuntimeIdRuntimeModule, SystemContextRuntimeModule, + CreateScriptRuntimeModule, CreateScriptUrlRuntimeModule, DefinePropertyGettersRuntimeModule, + EnsureChunkRuntimeModule, GetChunkFilenameRuntimeModule, GetChunkUpdateFilenameRuntimeModule, + GetFullHashRuntimeModule, GetMainFilenameRuntimeModule, GetTrustedTypesPolicyRuntimeModule, + GlobalRuntimeModule, HarmonyModuleDecoratorRuntimeModule, HasOwnPropertyRuntimeModule, + LoadScriptRuntimeModule, MakeNamespaceObjectRuntimeModule, NodeModuleDecoratorRuntimeModule, + NonceRuntimeModule, OnChunkLoadedRuntimeModule, PublicPathRuntimeModule, + RelativeUrlRuntimeModule, RuntimeIdRuntimeModule, SystemContextRuntimeModule, }; static GLOBALS_ON_REQUIRE: LazyLock> = LazyLock::new(|| { @@ -407,6 +407,9 @@ fn runtime_requirements_in_tree( compilation .add_runtime_module(chunk_ukey, CreateScriptUrlRuntimeModule::default().boxed())?; } + RuntimeGlobals::CREATE_SCRIPT => { + compilation.add_runtime_module(chunk_ukey, CreateScriptRuntimeModule::default().boxed())?; + } RuntimeGlobals::ON_CHUNKS_LOADED => { compilation .add_runtime_module(chunk_ukey, OnChunkLoadedRuntimeModule::default().boxed())?; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 index 80adb4be5bf..94441ff128a 100644 --- a/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/overlay.test.js.snap.webpack5 @@ -516,6 +516,14 @@ exports[`overlay should not show initially, then show on an error, then show oth `; +exports[`overlay should not show overlay when Trusted Types are enabled, but policy is not allowed: page html 1`] = ` + +

webpack-dev-server is running...

+ + + +`; + exports[`overlay should show a warning after invalidation: overlay html 1`] = `
+
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show overlay when Trusted Types are enabled and the "require-trusted-types-for 'script'" header was used: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + +exports[`overlay should show overlay when Trusted Types are enabled: overlay html 1`] = ` + +
+
+ Compiled with problems: +
+ +
+
+
+ ERROR +
+
+ × Error: Error from compilation. Can't find 'test' module. │ at + Object.fn + (/tests/e2e/overlay.test.js::) + │ at SyncHook.callAsyncStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at SyncHook.callStageRange + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at QueriedHook.call + (/node_modules/.pnpm/@rspack+lite-tapable@1.0.0/node_modules/@rspack/lite-tapable/dist/index.js::) + │ at + /packages/rspack/dist/Compiler.js:: + │ at last.function + (/packages/rspack/dist/Compiler.js::) +
+
+
+
+ + +`; + +exports[`overlay should show overlay when Trusted Types are enabled: page html 1`] = ` + +

webpack-dev-server is running...

+ + + + + +`; + exports[`overlay should show warning when it is not filtered: overlay html 1`] = `
{ } }); - // it("should show overlay when Trusted Types are enabled", async () => { - // const compiler = webpack(trustedTypesConfig); - - // new ErrorPlugin().apply(compiler); - - // const devServerOptions = { - // port, - // client: { - // overlay: { - // trustedTypesPolicyName: "webpack#dev-overlay", - // }, - // }, - // }; - // const server = new Server(devServerOptions, compiler); - - // await server.start(); - - // const { page, browser } = await runBrowser(); - - // try { - // const consoleMessages = []; - - // page.on("console", (message) => { - // consoleMessages.push(message.text()); - // }); - - // await page.goto(`http://localhost:${port}/`, { - // waitUntil: "networkidle0", - // }); - - // // Delay for the overlay to appear - // await delay(1000); - - // const pageHtml = await page.evaluate(() => document.body.outerHTML); - // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); - // const overlayFrame = await overlayHandle.contentFrame(); - // const overlayHtml = await overlayFrame.evaluate( - // () => document.body.outerHTML, - // ); - - // expect( - // consoleMessages.filter((item) => - // /requires 'TrustedHTML' assignment/.test(item), - // ), - // ).toHaveLength(0); - // expect( - // await prettier.format(pageHtml, { - // parser: "html", - // plugins: [prettierHTML, prettierCSS], - // }), - // ).toMatchSnapshot("page html"); - // expect( - // await prettier.format(overlayHtml, { - // parser: "html", - // plugins: [prettierHTML, prettierCSS], - // }), - // ).toMatchSnapshot("overlay html"); - // } catch (error) { - // throw error; - // } finally { - // await browser.close(); - // await server.stop(); - // } - // }); - - // it("should show overlay when Trusted Types are enabled and the \"require-trusted-types-for 'script'\" header was used", async () => { - // const compiler = webpack(trustedTypesConfig); - - // new ErrorPlugin().apply(compiler); - - // const devServerOptions = { - // port, - // headers: [ - // { - // key: "Content-Security-Policy", - // value: "require-trusted-types-for 'script'", - // }, - // ], - // client: { - // overlay: { - // trustedTypesPolicyName: "webpack#dev-overlay", - // }, - // }, - // }; - // const server = new Server(devServerOptions, compiler); - - // await server.start(); - - // const { page, browser } = await runBrowser(); - - // try { - // const consoleMessages = []; - - // page.on("console", (message) => { - // consoleMessages.push(message.text()); - // }); - - // await page.goto(`http://localhost:${port}/`, { - // waitUntil: "networkidle0", - // }); - - // // Delay for the overlay to appear - // await delay(1000); - - // const pageHtml = await page.evaluate(() => document.body.outerHTML); - // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); - // const overlayFrame = await overlayHandle.contentFrame(); - // const overlayHtml = await overlayFrame.evaluate( - // () => document.body.outerHTML, - // ); - - // await page.goto(`http://localhost:${port}/`, { - // waitUntil: "networkidle0", - // }); - - // expect( - // consoleMessages.filter((item) => - // /requires 'TrustedHTML' assignment/.test(item), - // ), - // ).toHaveLength(0); - // expect( - // await prettier.format(pageHtml, { - // parser: "html", - // plugins: [prettierHTML, prettierCSS], - // }), - // ).toMatchSnapshot("page html"); - // expect( - // await prettier.format(overlayHtml, { - // parser: "html", - // plugins: [prettierHTML, prettierCSS], - // }), - // ).toMatchSnapshot("overlay html"); - // } catch (error) { - // throw error; - // } finally { - // await browser.close(); - // await server.stop(); - // } - // }); - - // it("should not show overlay when Trusted Types are enabled, but policy is not allowed", async () => { - // const compiler = webpack(trustedTypesConfig); - - // new ErrorPlugin().apply(compiler); - - // const devServerOptions = { - // port, - // client: { - // overlay: { - // trustedTypesPolicyName: "disallowed-policy", - // }, - // }, - // }; - // const server = new Server(devServerOptions, compiler); - - // await server.start(); - - // const { page, browser } = await runBrowser(); - - // try { - // await page.goto(`http://localhost:${port}/`, { - // waitUntil: "networkidle0", - // }); - - // // Delay for the overlay to appear - // await delay(1000); - - // const pageHtml = await page.evaluate(() => document.body.outerHTML); - // const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); - // expect(overlayHandle).toBe(null); - // expect( - // await prettier.format(pageHtml, { - // parser: "html", - // plugins: [prettierHTML, prettierCSS], - // }), - // ).toMatchSnapshot("page html"); - // } catch (error) { - // throw error; - // } finally { - // await browser.close(); - // await server.stop(); - // } - // }); + it("should show overlay when Trusted Types are enabled", async () => { + const compiler = webpack(trustedTypesConfig); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + trustedTypesPolicyName: "webpack#dev-overlay" + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message.text()); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + expect( + consoleMessages.filter(item => + /requires 'TrustedHTML' assignment/.test(item) + ) + ).toHaveLength(0); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should show overlay when Trusted Types are enabled and the \"require-trusted-types-for 'script'\" header was used", async () => { + const compiler = webpack(trustedTypesConfig); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + headers: [ + { + key: "Content-Security-Policy", + value: "require-trusted-types-for 'script'" + } + ], + client: { + overlay: { + trustedTypesPolicyName: "webpack#dev-overlay" + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message.text()); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + const overlayFrame = await overlayHandle.contentFrame(); + const overlayHtml = await overlayFrame.evaluate( + () => document.body.outerHTML + ); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + consoleMessages.filter(item => + /requires 'TrustedHTML' assignment/.test(item) + ) + ).toHaveLength(0); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + expect( + await prettier.format(overlayHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("overlay html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should not show overlay when Trusted Types are enabled, but policy is not allowed", async () => { + const compiler = webpack(trustedTypesConfig); + + new ErrorPlugin().apply(compiler); + + const devServerOptions = { + port, + client: { + overlay: { + trustedTypesPolicyName: "disallowed-policy" + } + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + // Delay for the overlay to appear + await delay(1000); + + const pageHtml = await page.evaluate(() => document.body.outerHTML); + const overlayHandle = await page.$("#webpack-dev-server-client-overlay"); + expect(overlayHandle).toBe(null); + expect( + await prettier.format(pageHtml, { + parser: "html", + plugins: [prettierHTML, prettierCSS] + }) + ).toMatchSnapshot("page html"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); it('should show an error when "client.overlay.errors" is "true"', async () => { const compiler = webpack(config); diff --git a/tests/webpack-test/configCases/trusted-types/devtool-eval/test.filter.js b/tests/webpack-test/configCases/trusted-types/devtool-eval/test.filter.js deleted file mode 100644 index 3be456dcd23..00000000000 --- a/tests/webpack-test/configCases/trusted-types/devtool-eval/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => {return false} \ No newline at end of file diff --git a/tests/webpack-test/configCases/trusted-types/web-worker/test.filter.js b/tests/webpack-test/configCases/trusted-types/web-worker/test.filter.js deleted file mode 100644 index 4bee28e9aa7..00000000000 --- a/tests/webpack-test/configCases/trusted-types/web-worker/test.filter.js +++ /dev/null @@ -1,7 +0,0 @@ -// var supportsWorker = require("../../../helpers/supportsWorker"); - -// module.exports = function (config) { -// return supportsWorker(); -// }; - -module.exports = () => {return false} \ No newline at end of file From ef8214451baafdbf8539a514870548ea2132c428 Mon Sep 17 00:00:00 2001 From: Fy <1114550440@qq.com> Date: Thu, 12 Sep 2024 12:29:48 +0800 Subject: [PATCH 29/51] fix: do not use affected for side-effects-flag-plugin (#7865) fix: do not use cacheUnaffected for side-effects-flag-plugin --- .../src/plugin/side_effects_flag_plugin.rs | 24 +++----- .../__snapshots__/web/0.snap.txt | 12 ++++ .../__snapshots__/web/1.snap.txt | 58 +++++++++++++++++++ .../foo.js | 1 + .../index.js | 16 +++++ .../module.js | 6 ++ .../package.json | 1 + .../reexports-deep.js | 2 + .../reexports.js | 1 + .../rspack.config.js | 12 ++++ 10 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js create mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js diff --git a/crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs b/crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs index 7ad3f586fa6..cc5772ec35d 100644 --- a/crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs +++ b/crates/rspack_plugin_javascript/src/plugin/side_effects_flag_plugin.rs @@ -656,23 +656,13 @@ async fn nmf_module( #[plugin_hook(CompilationOptimizeDependencies for SideEffectsFlagPlugin)] fn optimize_dependencies(&self, compilation: &mut Compilation) -> Result> { - let mut modules: IdentifierSet = if compilation.options.new_incremental_enabled() { - compilation - .unaffected_modules_cache - .get_affected_modules_with_module_graph() - .lock() - .expect("should lock") - .iter() - .copied() - .collect() - } else { - compilation - .get_module_graph() - .modules() - .keys() - .copied() - .collect() - }; + // TODO: use affected module optimization + let mut modules: IdentifierSet = compilation + .get_module_graph() + .modules() + .keys() + .copied() + .collect(); let mut new_connections = Default::default(); for module in modules.clone() { optimize_incoming_connections(module, &mut modules, &mut new_connections, compilation); diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt new file mode 100644 index 00000000000..6c0391979e0 --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt @@ -0,0 +1,12 @@ +# Case re-export-optimization-for-inaffected-module: Step 0 + +## Changed Files + + +## Asset Files +- Bundle: bundle.js + +## Manifest + + +## Update \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt new file mode 100644 index 00000000000..9306de4e18c --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt @@ -0,0 +1,58 @@ +# Case re-export-optimization-for-inaffected-module: Step 1 + +## Changed Files +- module.js + +## Asset Files +- Bundle: bundle.js +- Manifest: main.LAST_HASH.hot-update.json, size: 28 +- Update: main.LAST_HASH.hot-update.js, size: 616 + +## Manifest + +### main.LAST_HASH.hot-update.json + +```json +{"c":["main"],"r":[],"m":[]} +``` + + +## Update + + +### main.LAST_HASH.hot-update.js + +#### Changed Modules +- ./module.js + +#### Changed Runtime Modules +- webpack/runtime/get_full_hash + +#### Changed Content +```js +"use strict"; +self["webpackHotUpdate"]('main', { +"./module.js": (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) { +__webpack_require__.r(__webpack_exports__); +__webpack_require__.d(__webpack_exports__, { + value: function() { return value; } +}); +/* harmony import */var _reexports__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./reexports */ "./foo.js"); + +const value = '' + _reexports__WEBPACK_IMPORTED_MODULE_0__.v; + + +}), + +},function(__webpack_require__) { +// webpack/runtime/get_full_hash +(() => { +__webpack_require__.h = function () { + return "CURRENT_HASH"; +}; + +})(); + +} +); +``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js new file mode 100644 index 00000000000..221452b02f2 --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js @@ -0,0 +1 @@ +export const v = 'foo' diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js new file mode 100644 index 00000000000..34ce108bd13 --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js @@ -0,0 +1,16 @@ +import {value} from "./module"; + +let v = value; +module.hot.accept('./module', () => { + v = value +}); + +it("should auto-reexport an ES6 imported value on accept with newTreeshaking", async function (done) { + expect(v).toBe("foo"); + NEXT( + require("../../update")(done, true, () => { + expect(v).toBe("foo"); + done(); + }) + ); +}); diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js new file mode 100644 index 00000000000..de10e4d3031 --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js @@ -0,0 +1,6 @@ +import {v} from './reexports' +// +export const value = '' + v; +--- +import {v} from './reexports' +export const value = '' + v; diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json new file mode 100644 index 00000000000..9e26dfeeb6e --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js new file mode 100644 index 00000000000..e386b252975 --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js @@ -0,0 +1,2 @@ +import {v} from './foo'; +export {v}; diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js new file mode 100644 index 00000000000..d4c2fecf76c --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js @@ -0,0 +1 @@ +export { v } from "./reexports-deep"; diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js new file mode 100644 index 00000000000..93f66ea6acc --- /dev/null +++ b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js @@ -0,0 +1,12 @@ +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + optimization: { + sideEffects: true, + providedExports: true, + }, + experiments: { + rspackFuture: { + newIncremental: true + } + } +}; From 501c12f261a3f8512ea1c7891df8c32df9ad376d Mon Sep 17 00:00:00 2001 From: Hana Date: Thu, 12 Sep 2024 15:01:38 +0800 Subject: [PATCH 30/51] fix: should forward sourcemaps for refresh loaders (#7872) --- .../rspack_loader_preact_refresh/src/lib.rs | 9 +- crates/rspack_loader_react_refresh/src/lib.rs | 3 +- packages/rspack-test-tools/package.json | 2 + .../preact-refresh/index.jsx | 9 + .../preact-refresh/rspack.config.js | 57 +++++ .../react-refresh/index.jsx | 9 + .../react-refresh/rspack.config.js | 44 ++++ pnpm-lock.yaml | 195 +++++++++++++++++- 8 files changed, 314 insertions(+), 14 deletions(-) create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/index.jsx create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/index.jsx create mode 100644 packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/rspack.config.js diff --git a/crates/rspack_loader_preact_refresh/src/lib.rs b/crates/rspack_loader_preact_refresh/src/lib.rs index 8c06dba9e33..3861ff41b69 100644 --- a/crates/rspack_loader_preact_refresh/src/lib.rs +++ b/crates/rspack_loader_preact_refresh/src/lib.rs @@ -27,13 +27,14 @@ impl PreactRefreshLoader { #[async_trait::async_trait] impl Loader for PreactRefreshLoader { async fn run(&self, loader_context: &mut LoaderContext) -> Result<()> { - let content = loader_context - .take_content() - .expect("Content should be available"); + let Some(content) = loader_context.take_content() else { + return Ok(()); + }; let mut source = content.try_into_string()?; source += "\n"; source += include_str!("runtime.js"); - loader_context.finish_with(source); + let sm = loader_context.take_source_map(); + loader_context.finish_with((source, sm)); Ok(()) } } diff --git a/crates/rspack_loader_react_refresh/src/lib.rs b/crates/rspack_loader_react_refresh/src/lib.rs index 8f92ec12bd0..aac81039a2b 100644 --- a/crates/rspack_loader_react_refresh/src/lib.rs +++ b/crates/rspack_loader_react_refresh/src/lib.rs @@ -42,7 +42,8 @@ Promise.resolve().then(function() { $ReactRefreshRuntime$.refresh(__webpack_module__.id, __webpack_module__.hot); }); "#; - loader_context.finish_with(source); + let sm = loader_context.take_source_map(); + loader_context.finish_with((source, sm)); Ok(()) } } diff --git a/packages/rspack-test-tools/package.json b/packages/rspack-test-tools/package.json index e33e062458d..7f853cdc25d 100644 --- a/packages/rspack-test-tools/package.json +++ b/packages/rspack-test-tools/package.json @@ -73,6 +73,8 @@ "@monaco-editor/react": "^4.6.0", "@rspack/cli": "workspace:*", "@rspack/core": "workspace:*", + "@rspack/plugin-react-refresh": "1.0.0", + "@rspack/plugin-preact-refresh": "1.0.0", "@swc/helpers": "0.5.8", "@swc/plugin-remove-console": "^2.0.8", "@types/babel__generator": "7.6.8", diff --git a/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/index.jsx b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/index.jsx new file mode 100644 index 00000000000..e3fbccb5dad --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/index.jsx @@ -0,0 +1,9 @@ +function component () { + return
+} + +it('should work with preact-refresh', () => { + const fs = require("fs") + let map = fs.readFileSync(__filename + ".map", "utf-8") + expect(map).toContain(STUB) +}) diff --git a/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/rspack.config.js b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/rspack.config.js new file mode 100644 index 00000000000..e6f2673a6a9 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/preact-refresh/rspack.config.js @@ -0,0 +1,57 @@ +const rspack = require("@rspack/core") +const PreactRefreshPlugin = require("@rspack/plugin-preact-refresh"); +const { ConcatSource, RawSource } = require("webpack-sources") +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: "./index.jsx", + mode: "development", + resolve: { + extensions: ["...", ".ts", ".tsx", ".jsx"] + }, + devtool: "source-map", + module: { + rules: [ + { + test: /\.jsx$/, + loader: "builtin:swc-loader", + options: { + jsc: { + parser: { + syntax: "ecmascript", + jsx: true, + sourceMap: true, + }, + transform: { + react: { + runtime: "classic", + pragma: "React.createElement", + pragmaFrag: "React.Fragment", + throwIfNamespace: true, + useBuiltins: false + } + } + } + } + } + ] + }, + plugins: [ + new rspack.HotModuleReplacementPlugin(), + new PreactRefreshPlugin(), + new rspack.DefinePlugin({ + STUB: JSON.stringify("
") + }), + { + apply(compiler) { + compiler.hooks.compilation.tap("_", (compilation) => { + compilation.hooks.processAssets.tap("_", (assets) => { + compilation.updateAsset("bundle0.js",new ConcatSource( + new RawSource("const self = globalThis;"), // mock self to NodeJs specific global object + assets["bundle0.js"] + )) + }) + }) + } + } + ] +}; diff --git a/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/index.jsx b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/index.jsx new file mode 100644 index 00000000000..26c29c3c8e9 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/index.jsx @@ -0,0 +1,9 @@ +function component () { + return
+} + +it('should work with react refresh', () => { + const fs = require("fs") + let map = fs.readFileSync(__filename + ".map", "utf-8") + expect(map).toContain(STUB) +}) diff --git a/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/rspack.config.js b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/rspack.config.js new file mode 100644 index 00000000000..a8af427fb40 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/builtin-swc-loader/react-refresh/rspack.config.js @@ -0,0 +1,44 @@ +const rspack = require("@rspack/core") +const ReactRefreshPlugin = require("@rspack/plugin-react-refresh"); +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: "./index.jsx", + mode: "development", + resolve: { + extensions: ["...", ".ts", ".tsx", ".jsx"] + }, + devtool: "source-map", + module: { + rules: [ + { + test: /\.jsx$/, + loader: "builtin:swc-loader", + options: { + jsc: { + parser: { + syntax: "ecmascript", + jsx: true, + sourceMap: true, + }, + transform: { + react: { + runtime: "classic", + pragma: "React.createElement", + pragmaFrag: "React.Fragment", + throwIfNamespace: true, + useBuiltins: false + } + } + } + } + } + ] + }, + plugins: [ + new rspack.HotModuleReplacementPlugin(), + new ReactRefreshPlugin(), + new rspack.DefinePlugin({ + STUB: JSON.stringify("
") + }) + ] +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd471959f1e..18c649dce14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -682,6 +682,12 @@ importers: '@rspack/core': specifier: workspace:* version: link:../rspack + '@rspack/plugin-preact-refresh': + specifier: 1.0.0 + version: 1.0.0(@prefresh/core@1.5.2(preact@10.23.2))(@prefresh/utils@1.2.0) + '@rspack/plugin-react-refresh': + specifier: 1.0.0 + version: 1.0.0(react-refresh@0.14.0) '@swc/helpers': specifier: 0.5.8 version: 0.5.8 @@ -1045,7 +1051,7 @@ importers: version: 3.36.1 css-loader: specifier: ^6.11.0 - version: 6.11.0(@rspack/core@1.0.3)(webpack@5.94.0(webpack-cli@5.1.4)) + version: 6.11.0(@rspack/core@1.0.4)(webpack@5.94.0(webpack-cli@5.1.4)) date-fns: specifier: ^2.29.3 version: 2.30.0 @@ -2805,6 +2811,14 @@ packages: '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@prefresh/core@1.5.2': + resolution: {integrity: sha512-A/08vkaM1FogrCII5PZKCrygxSsc11obExBScm3JF1CryK2uDS3ZXeni7FeKCx1nYdUkj4UcJxzPzc1WliMzZA==} + peerDependencies: + preact: ^10.0.0 + + '@prefresh/utils@1.2.0': + resolution: {integrity: sha512-KtC/fZw+oqtwOLUFM9UtiitB0JsVX0zLKNyRTA332sqREqSALIIQQxdUCS1P3xR/jT1e2e8/5rwH6gdcMLEmsQ==} + '@puppeteer/browsers@2.4.0': resolution: {integrity: sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==} engines: {node: '>=18'} @@ -2913,49 +2927,97 @@ packages: cpu: [arm64] os: [darwin] + '@rspack/binding-darwin-arm64@1.0.4': + resolution: {integrity: sha512-HR4OVarDhLYAd3GRus26mGjaoEkvFGSdhRHWPXkTqPWT1QIx+cMTDe2dUnEb/CZyvpH0afLBlVHrxqUBNiLT/Q==} + cpu: [arm64] + os: [darwin] + '@rspack/binding-darwin-x64@1.0.3': resolution: {integrity: sha512-Ke8Tw3+j5YFFIHjlqrEDnW5fbZU14s+l5LhLVaw6gVXH4yAAyFaZejaIViGi5fhKkdKmCXm0nVTK1KhhASZxAw==} cpu: [x64] os: [darwin] + '@rspack/binding-darwin-x64@1.0.4': + resolution: {integrity: sha512-3L/vrzrcBpg1h7SqD+9m7CB0UlkDt3aOyJGZc0vwLOQM8IezRsawEoidyN9KyYhpDzBZHzLAUnsva33BcbFJYQ==} + cpu: [x64] + os: [darwin] + '@rspack/binding-linux-arm64-gnu@1.0.3': resolution: {integrity: sha512-Ydm6rsBnPYlKfWtz6sPRgAgJ5fQ+zFSHplR4bFlARIOXeWPn7ckQvFZrmKexuR0ULjG3Z4sbfrU6udc2MAWvig==} cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-gnu@1.0.4': + resolution: {integrity: sha512-Ggo2tLJKOFNsdcg9H3eYSUy03Wrq0PMVIlm50p/LLAMlfYm3kxBDNv7fP13fxlUe/oqXtFcVOlmMezbMH4oz8w==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-arm64-musl@1.0.3': resolution: {integrity: sha512-2aS65Xne3W6qJJ5PN5oL/bMbkUeYpsQduT+ML6vY6hqvi6W6wYtkvHwscQ4HisxKQFWdB2bsE1+UT34XgHpijw==} cpu: [arm64] os: [linux] + '@rspack/binding-linux-arm64-musl@1.0.4': + resolution: {integrity: sha512-2FeDh8/THJTH2N4XpFC9oRvdDlkOw6bQ+vS8x1jLUXj+CDwqmUiECjh+78o4qwe1EH2egWDmkq84RQ+KoR37mQ==} + cpu: [arm64] + os: [linux] + '@rspack/binding-linux-x64-gnu@1.0.3': resolution: {integrity: sha512-6UWii/GBkV0B98RSjJr9Za5Y8rvU1vQpE5+8vb26pvo3Sh3kvRfOmSeIFyqR3I92er5SQKmEp8uggb74st6QGQ==} cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-gnu@1.0.4': + resolution: {integrity: sha512-k2YQ579fDfXBw4KJMvZQQPShFUSln7wqtG59ltNLBejgFSxOtlMfLhY5oSuRvFCtsAS4SQUPOmUmfW/0dpCa7A==} + cpu: [x64] + os: [linux] + '@rspack/binding-linux-x64-musl@1.0.3': resolution: {integrity: sha512-tpKGPJEe6vkkH3bsDz+l7xndNrOGxDvbDVOXV0uSESnRr6Pef5253Bi5PNooGLSaWQlXVDdazhi6x1beVHI2eg==} cpu: [x64] os: [linux] + '@rspack/binding-linux-x64-musl@1.0.4': + resolution: {integrity: sha512-1Pmuq3qnE45U7uhCJNtRbDiQVF+U4BXbsA+ISNd3Gp/MUZqpuOOgrND9OWw7d+xKRifzEMtd81DaPMkrcXyRvw==} + cpu: [x64] + os: [linux] + '@rspack/binding-win32-arm64-msvc@1.0.3': resolution: {integrity: sha512-9FwP64T6yeq3cG1JQG0VagTMuJxJCT45G9LN5RTJ2kxJ4T28vL7uEc9usPpXOyd6xpbzTKXX0mVxL+c8x0EhZQ==} cpu: [arm64] os: [win32] + '@rspack/binding-win32-arm64-msvc@1.0.4': + resolution: {integrity: sha512-3tBP4xTAe8XRAlchMj2nlQXy2GOICA5StFK0kQMRId4V3xfgjlKDb/dMHMEHH4R1to1f9er9R0cRydUNP5AnbQ==} + cpu: [arm64] + os: [win32] + '@rspack/binding-win32-ia32-msvc@1.0.3': resolution: {integrity: sha512-nXbeh0206bGiwV1vgY8UDk92sZ2yMvJenevnnLtGFSMTRQ46Z2f9n+mUO1GlvpanR1HAfhgPddlKasIncTJmPQ==} cpu: [ia32] os: [win32] + '@rspack/binding-win32-ia32-msvc@1.0.4': + resolution: {integrity: sha512-VCXXhOh9aWg31RfvJL87cvqIeL8h+gG2LPkXCJ8kzvQItLg6vlXABCGHf3NlaVUnDtAHnhsAT1sOHiD8G/kMfg==} + cpu: [ia32] + os: [win32] + '@rspack/binding-win32-x64-msvc@1.0.3': resolution: {integrity: sha512-htBi4xt+iXD/NCEo/q4fYSg5YfXymK9P9zI36NFvfguQbhwqy4JgBx0IorjDFl5qvG70sdUzY7x98DJEseGScQ==} cpu: [x64] os: [win32] + '@rspack/binding-win32-x64-msvc@1.0.4': + resolution: {integrity: sha512-hlhAZVpL1MmGJUZ730JvOL6zc12BlfIIwRNnn0PkCrNSxNOYknkfKVix8CfPU9r8L2W8MOJfsbtjteCiJmJnQA==} + cpu: [x64] + os: [win32] + '@rspack/binding@1.0.3': resolution: {integrity: sha512-wRLUDyi/6jFDDZJIov4uh9H9hJNk7JKDEhaMLM/5lJzzWsTLBB/q6JB1VAdIzOzBhYsU8iIMEVuG3Uih1H43uw==} + '@rspack/binding@1.0.4': + resolution: {integrity: sha512-YbVyVWvYXCAqUqIyBPEwl+M0js4BqVAxqnsRiZG861MXrqNyYbGOMTcc3Pc6D0SW/gkcWugGWzQ5jgMjAIhHzw==} + '@rspack/core@1.0.3': resolution: {integrity: sha512-BqyBSrcTtsm1EDI10TrK6tEfNsy5vEnVDnbI0HBuQBH1zhKblnvsu6Y2bbS9+OGmB+OPEr76CmuZtzb+7V3wrQ==} engines: {node: '>=16.0.0'} @@ -2965,10 +3027,25 @@ packages: '@swc/helpers': optional: true + '@rspack/core@1.0.4': + resolution: {integrity: sha512-/R3JenF5wJSj3DPxiewyIPGzuZV336XpRORjUAOvbHPK6zea8Eeqcx6RopWM6TMikRYdZOHThKV99tyi4QLsMg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + '@rspack/lite-tapable@1.0.0': resolution: {integrity: sha512-7MZf4lburSUZoEenwazwUDKHhqyfnLCGnQ/tKcUtztfmVzfjZfRn/EaiT0AKkYGnL2U8AGsw89oUeVyvaOLVCw==} engines: {node: '>=16.0.0'} + '@rspack/plugin-preact-refresh@1.0.0': + resolution: {integrity: sha512-FCdZTM0Z5/Am8jgTLyOQA5IDSysQNw19u87mRVem8r9tgzaEiGUv9Q7IIRnpCqNwzofeh/I/Qx/SneS7UpLQeQ==} + peerDependencies: + '@prefresh/core': ^1.5.0 + '@prefresh/utils': ^1.2.0 + '@rspack/plugin-react-refresh@1.0.0': resolution: {integrity: sha512-WvXkLewW5G0Mlo5H1b251yDh5FFiH4NDAbYlFpvFjcuXX2AchZRf9zdw57BDE/ADyWsJgA8kixN/zZWBTN3iYA==} peerDependencies: @@ -7701,6 +7778,9 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + preact@10.23.2: + resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} + prebundle@1.1.0: resolution: {integrity: sha512-yTfRjx0+xiveeb7kO77OcODVB8RSHMKIiVl/qferU7ZHw4Y8pycXkCAtPDViF8YDo0a8ViDpm4C1O9PFKCw1ig==} hasBin: true @@ -11906,6 +11986,12 @@ snapshots: '@polka/url@1.0.0-next.25': {} + '@prefresh/core@1.5.2(preact@10.23.2)': + dependencies: + preact: 10.23.2 + + '@prefresh/utils@1.2.0': {} + '@puppeteer/browsers@2.4.0': dependencies: debug: 4.3.6 @@ -11988,30 +12074,57 @@ snapshots: '@rspack/binding-darwin-arm64@1.0.3': optional: true + '@rspack/binding-darwin-arm64@1.0.4': + optional: true + '@rspack/binding-darwin-x64@1.0.3': optional: true + '@rspack/binding-darwin-x64@1.0.4': + optional: true + '@rspack/binding-linux-arm64-gnu@1.0.3': optional: true + '@rspack/binding-linux-arm64-gnu@1.0.4': + optional: true + '@rspack/binding-linux-arm64-musl@1.0.3': optional: true + '@rspack/binding-linux-arm64-musl@1.0.4': + optional: true + '@rspack/binding-linux-x64-gnu@1.0.3': optional: true + '@rspack/binding-linux-x64-gnu@1.0.4': + optional: true + '@rspack/binding-linux-x64-musl@1.0.3': optional: true + '@rspack/binding-linux-x64-musl@1.0.4': + optional: true + '@rspack/binding-win32-arm64-msvc@1.0.3': optional: true + '@rspack/binding-win32-arm64-msvc@1.0.4': + optional: true + '@rspack/binding-win32-ia32-msvc@1.0.3': optional: true + '@rspack/binding-win32-ia32-msvc@1.0.4': + optional: true + '@rspack/binding-win32-x64-msvc@1.0.3': optional: true + '@rspack/binding-win32-x64-msvc@1.0.4': + optional: true + '@rspack/binding@1.0.3': optionalDependencies: '@rspack/binding-darwin-arm64': 1.0.3 @@ -12024,6 +12137,19 @@ snapshots: '@rspack/binding-win32-ia32-msvc': 1.0.3 '@rspack/binding-win32-x64-msvc': 1.0.3 + '@rspack/binding@1.0.4': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.0.4 + '@rspack/binding-darwin-x64': 1.0.4 + '@rspack/binding-linux-arm64-gnu': 1.0.4 + '@rspack/binding-linux-arm64-musl': 1.0.4 + '@rspack/binding-linux-x64-gnu': 1.0.4 + '@rspack/binding-linux-x64-musl': 1.0.4 + '@rspack/binding-win32-arm64-msvc': 1.0.4 + '@rspack/binding-win32-ia32-msvc': 1.0.4 + '@rspack/binding-win32-x64-msvc': 1.0.4 + optional: true + '@rspack/core@1.0.3(@swc/helpers@0.5.12)': dependencies: '@module-federation/runtime-tools': 0.5.1 @@ -12033,8 +12159,21 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.12 + '@rspack/core@1.0.4': + dependencies: + '@module-federation/runtime-tools': 0.5.1 + '@rspack/binding': 1.0.4 + '@rspack/lite-tapable': 1.0.0 + caniuse-lite: 1.0.30001660 + optional: true + '@rspack/lite-tapable@1.0.0': {} + '@rspack/plugin-preact-refresh@1.0.0(@prefresh/core@1.5.2(preact@10.23.2))(@prefresh/utils@1.2.0)': + dependencies: + '@prefresh/core': 1.5.2(preact@10.23.2) + '@prefresh/utils': 1.2.0 + '@rspack/plugin-react-refresh@1.0.0(react-refresh@0.14.0)': dependencies: error-stack-parser: 2.1.4 @@ -12222,6 +12361,23 @@ snapshots: '@swc/core-win32-x64-msvc@1.4.0': optional: true + '@swc/core@1.4.0': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.6 + optionalDependencies: + '@swc/core-darwin-arm64': 1.4.0 + '@swc/core-darwin-x64': 1.4.0 + '@swc/core-linux-arm-gnueabihf': 1.4.0 + '@swc/core-linux-arm64-gnu': 1.4.0 + '@swc/core-linux-arm64-musl': 1.4.0 + '@swc/core-linux-x64-gnu': 1.4.0 + '@swc/core-linux-x64-musl': 1.4.0 + '@swc/core-win32-arm64-msvc': 1.4.0 + '@swc/core-win32-ia32-msvc': 1.4.0 + '@swc/core-win32-x64-msvc': 1.4.0 + optional: true + '@swc/core@1.4.0(@swc/helpers@0.5.1)': dependencies: '@swc/counter': 0.1.3 @@ -14787,7 +14943,7 @@ snapshots: semver: 7.6.2 webpack: 5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.1))(webpack-cli@5.1.4(webpack@5.94.0)) - css-loader@6.11.0(@rspack/core@1.0.3)(webpack@5.94.0(webpack-cli@5.1.4)): + css-loader@6.11.0(@rspack/core@1.0.4)(webpack@5.94.0(webpack-cli@5.1.4)): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 @@ -14798,7 +14954,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.0 optionalDependencies: - '@rspack/core': 1.0.3(@swc/helpers@0.5.12) + '@rspack/core': 1.0.4 webpack: 5.94.0(webpack-cli@5.1.4) css-loader@6.11.0(@rspack/core@packages+rspack)(webpack@5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.8))(webpack-cli@5.1.4(webpack@5.94.0))): @@ -16681,7 +16837,7 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.12.7 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.0.2) + ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.0.2) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -17945,7 +18101,7 @@ snapshots: yaml: 2.4.1 optionalDependencies: postcss: 8.4.38 - ts-node: 10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.0.2) + ts-node: 10.9.2(@types/node@20.12.7)(typescript@5.0.2) postcss-loader@7.3.4(postcss@8.4.38)(typescript@4.9.5)(webpack@5.94.0(@swc/core@1.4.0(@swc/helpers@0.5.1))(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -18024,6 +18180,8 @@ snapshots: picocolors: 1.0.0 source-map-js: 1.2.0 + preact@10.23.2: {} + prebundle@1.1.0(typescript@5.0.2): dependencies: '@vercel/ncc': 0.38.1 @@ -19568,7 +19726,7 @@ snapshots: terser: 5.27.2 webpack: 5.92.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0)) optionalDependencies: - '@swc/core': 1.4.0(@swc/helpers@0.5.1) + '@swc/core': 1.4.0 terser-webpack-plugin@5.3.10(@swc/core@1.4.0)(webpack@5.94.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -19579,7 +19737,7 @@ snapshots: terser: 5.27.2 webpack: 5.94.0(@swc/core@1.4.0)(webpack-cli@5.1.4(webpack@5.94.0)) optionalDependencies: - '@swc/core': 1.4.0(@swc/helpers@0.5.1) + '@swc/core': 1.4.0 terser-webpack-plugin@5.3.10(webpack@5.94.0(webpack-cli@5.1.4(webpack@5.94.0))): dependencies: @@ -19768,7 +19926,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.4.0(@swc/helpers@0.5.1) + '@swc/core': 1.4.0 ts-node@10.9.2(@swc/core@1.4.0)(@types/node@20.12.7)(typescript@5.4.2): dependencies: @@ -19788,7 +19946,26 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.4.0(@swc/helpers@0.5.1) + '@swc/core': 1.4.0 + + ts-node@10.9.2(@types/node@20.12.7)(typescript@5.0.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.12.7 + acorn: 8.11.3 + acorn-walk: 8.3.2 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.0.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true tsc-alias@1.8.8: dependencies: From fa38b86a506dd8e16ec34527c7dcd4bd146bdd08 Mon Sep 17 00:00:00 2001 From: Hana Date: Thu, 12 Sep 2024 15:24:46 +0800 Subject: [PATCH 31/51] fix: should provide full error message with error name when `hideStack` is set to `true` (#7867) * fix: init * fix --- .../loader-emit-error/index.js | 10 ++++++++++ .../loader-emit-error/lib.js | 1 + .../loader-emit-error/my-loader.js | 4 ++++ .../loader-emit-error/rspack.config.js | 18 ++++++++++++++++++ .../loader-emit-error/stats.err | 9 +++++++++ .../loader-emit-hide-stack/index.js | 10 ++++++++++ .../loader-emit-hide-stack/lib.js | 1 + .../loader-emit-hide-stack/my-loader.js | 11 +++++++++++ .../loader-emit-hide-stack/rspack.config.js | 18 ++++++++++++++++++ .../loader-emit-hide-stack/stats.err | 5 +++++ .../loader-emit-warning/index.js | 10 ++++++++++ .../loader-emit-warning/lib.js | 1 + .../loader-emit-warning/my-loader.js | 4 ++++ .../loader-emit-warning/rspack.config.js | 18 ++++++++++++++++++ .../loader-emit-warning/stats.err | 9 +++++++++ packages/rspack/src/loader-runner/index.ts | 4 ---- packages/rspack/src/util/index.ts | 12 +++++++++++- .../cases/build-in-css-support/warnings.js | 2 +- 18 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/index.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/lib.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/my-loader.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/stats.err create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/index.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/lib.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/stats.err create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/index.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/lib.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/my-loader.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/rspack.config.js create mode 100644 packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/stats.err diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/index.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/index.js new file mode 100644 index 00000000000..f521e00df8a --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/index.js @@ -0,0 +1,10 @@ +it("should include loader thrown error", () => { + let errored = false; + try { + require("./lib"); + } catch (e) { + errored = true; + expect(e.message).toContain("Failed to load"); + } + expect(errored).toBeTruthy() +}); diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/lib.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/lib.js new file mode 100644 index 00000000000..ede7bc80d75 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/lib.js @@ -0,0 +1 @@ +export const lib = "lib"; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/my-loader.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/my-loader.js new file mode 100644 index 00000000000..f135ba2212d --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/my-loader.js @@ -0,0 +1,4 @@ +module.exports = function (context) { + this.emitError(new Error("Failed to load")); + return "" +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/rspack.config.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/rspack.config.js new file mode 100644 index 00000000000..69e7f91cadb --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/rspack.config.js @@ -0,0 +1,18 @@ +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + context: __dirname, + module: { + rules: [ + { + test: /lib\.js$/, + use: [ + { + loader: "./my-loader.js" + } + ] + } + ] + } +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/stats.err new file mode 100644 index 00000000000..1ec682d6041 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-error/stats.err @@ -0,0 +1,9 @@ +ERROR in ./lib.js + × ModuleError: Failed to load (from: /tests/diagnosticsCases/module-build-failed/loader-emit-error/my-loader.js) + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/index.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/index.js new file mode 100644 index 00000000000..f521e00df8a --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/index.js @@ -0,0 +1,10 @@ +it("should include loader thrown error", () => { + let errored = false; + try { + require("./lib"); + } catch (e) { + errored = true; + expect(e.message).toContain("Failed to load"); + } + expect(errored).toBeTruthy() +}); diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/lib.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/lib.js new file mode 100644 index 00000000000..ede7bc80d75 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/lib.js @@ -0,0 +1 @@ +export const lib = "lib"; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js new file mode 100644 index 00000000000..4ea41d2ac73 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js @@ -0,0 +1,11 @@ +module.exports = function (context) { + let e; + e = new Error("Failed to load"); + e.hideStack = true; + this.emitError(e); + + e = new Error("Failed to load"); + e.hideStack = true; + this.emitWarning(e); + return "" +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/rspack.config.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/rspack.config.js new file mode 100644 index 00000000000..69e7f91cadb --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/rspack.config.js @@ -0,0 +1,18 @@ +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + context: __dirname, + module: { + rules: [ + { + test: /lib\.js$/, + use: [ + { + loader: "./my-loader.js" + } + ] + } + ] + } +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/stats.err new file mode 100644 index 00000000000..77fcb98ad9c --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/stats.err @@ -0,0 +1,5 @@ +WARNING in ./lib.js + ⚠ ModuleWarning: Failed to load (from: /tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js) + +ERROR in ./lib.js + × ModuleError: Failed to load (from: /tests/diagnosticsCases/module-build-failed/loader-emit-hide-stack/my-loader.js) \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/index.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/index.js new file mode 100644 index 00000000000..f521e00df8a --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/index.js @@ -0,0 +1,10 @@ +it("should include loader thrown error", () => { + let errored = false; + try { + require("./lib"); + } catch (e) { + errored = true; + expect(e.message).toContain("Failed to load"); + } + expect(errored).toBeTruthy() +}); diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/lib.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/lib.js new file mode 100644 index 00000000000..ede7bc80d75 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/lib.js @@ -0,0 +1 @@ +export const lib = "lib"; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/my-loader.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/my-loader.js new file mode 100644 index 00000000000..50d47ab4c40 --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/my-loader.js @@ -0,0 +1,4 @@ +module.exports = function (context) { + this.emitWarning(new Error("Failed to load")); + return "" +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/rspack.config.js b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/rspack.config.js new file mode 100644 index 00000000000..69e7f91cadb --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/rspack.config.js @@ -0,0 +1,18 @@ +/** + * @type {import('@rspack/core').RspackOptions} + */ +module.exports = { + context: __dirname, + module: { + rules: [ + { + test: /lib\.js$/, + use: [ + { + loader: "./my-loader.js" + } + ] + } + ] + } +}; diff --git a/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/stats.err b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/stats.err new file mode 100644 index 00000000000..a6e8426f1ef --- /dev/null +++ b/packages/rspack-test-tools/tests/diagnosticsCases/module-build-failed/loader-emit-warning/stats.err @@ -0,0 +1,9 @@ +WARNING in ./lib.js + ⚠ ModuleWarning: Failed to load (from: /tests/diagnosticsCases/module-build-failed/loader-emit-warning/my-loader.js) + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx + │ at xxx \ No newline at end of file diff --git a/packages/rspack/src/loader-runner/index.ts b/packages/rspack/src/loader-runner/index.ts index 216db5d4e48..ba1d5b019de 100644 --- a/packages/rspack/src/loader-runner/index.ts +++ b/packages/rspack/src/loader-runner/index.ts @@ -618,12 +618,10 @@ export async function runLoaders( if (!(error instanceof Error)) { error = new NonErrorEmittedError(error); } - const hasStack = !!error.stack; error.name = "ModuleError"; error.message = `${error.message} (from: ${stringifyLoaderObject( loaderContext.loaders[loaderContext.loaderIndex] )})`; - !hasStack && Error.captureStackTrace(error); error = concatErrorMsgAndStack(error); (error as RspackError).moduleIdentifier = this._module.identifier(); compiler._lastCompilation!.__internal__pushDiagnostic({ @@ -636,12 +634,10 @@ export async function runLoaders( if (!(warning instanceof Error)) { warning = new NonErrorEmittedError(warning); } - const hasStack = !!warning.stack; warning.name = "ModuleWarning"; warning.message = `${warning.message} (from: ${stringifyLoaderObject( loaderContext.loaders[loaderContext.loaderIndex] )})`; - hasStack && Error.captureStackTrace(warning); warning = concatErrorMsgAndStack(warning); (warning as RspackError).moduleIdentifier = this._module.identifier(); compiler._lastCompilation!.__internal__pushDiagnostic({ diff --git a/packages/rspack/src/util/index.ts b/packages/rspack/src/util/index.ts index 790ac734ee7..9195ada166c 100644 --- a/packages/rspack/src/util/index.ts +++ b/packages/rspack/src/util/index.ts @@ -77,7 +77,17 @@ export function concatErrorMsgAndStack( } const hideStack = "hideStack" in err && err.hideStack; if (!hideStack && "stack" in err) { - err.message = err.stack || err.message; + // This is intended to be different than webpack, + // here we want to treat the almost the same as `Error.stack` just without the stack. + // Webpack uses `Error.message`, however it does not contain the `Error.prototype.name` + // `xxx` -> `Error: xxx`. So they behave the same even if `hideStack` is set to `true`. + err.message = err.stack || err.toString(); + } else { + // This is intended to be different than webpack, + // here we want to treat the almost the same as `Error.stack` just without the stack. + // Webpack uses `Error.message`, however it does not contain the `Error.prototype.name` + // `xxx` -> `Error: xxx`. So they behave the same even if `hideStack` is set to `true`. + err.message = err.toString(); } // maybe `null`, use `undefined` to compatible with `Option` err.stack = err.stack || undefined; diff --git a/tests/plugin-test/css-extract/cases/build-in-css-support/warnings.js b/tests/plugin-test/css-extract/cases/build-in-css-support/warnings.js index 6a736f53eda..7f2a178ae8d 100644 --- a/tests/plugin-test/css-extract/cases/build-in-css-support/warnings.js +++ b/tests/plugin-test/css-extract/cases/build-in-css-support/warnings.js @@ -4,7 +4,7 @@ // decide bailout on builtin css module module.exports = `WARNING in ./style.css - ⚠ use type 'css' and \`CssExtractRspackPlugin\` together, please set \`experiments.css\` to \`false\` or set \`{ type: "javascript/auto" }\` for rules with \`CssExtractRspackPlugin\` in your rspack config (now \`CssExtractRspackPlugin\` does nothing). + ⚠ ModuleWarning: use type 'css' and \`CssExtractRspackPlugin\` together, please set \`experiments.css\` to \`false\` or set \`{ type: "javascript/auto" }\` for rules with \`CssExtractRspackPlugin\` in your rspack config (now \`CssExtractRspackPlugin\` does nothing). WARNING in ./style.css ⚠ ModuleWarning: You can't use \`experiments.css\` (\`experiments.futureDefaults\` enable built-in CSS support by default) and \`css-loader\` together, please set \`experiments.css\` to \`false\` or set \`{ type: "javascript/auto" }\` for rules with \`css-loader\` in your webpack config (now css-loader does nothing). `; \ No newline at end of file From 8bfd6391f9ef2cbfd9214b78c8e4994c504c0e11 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Thu, 12 Sep 2024 16:27:26 +0800 Subject: [PATCH 32/51] test(dev-sever): add e2e test cases part 3 (#7833) --- .gitignore | 2 + packages/rspack-dev-server/jest.config.js | 20 +- .../__snapshots__/port.test.js.snap.webpack5 | 61 + ...and-client-transport.test.js.snap.webpack5 | 107 + .../server.test.js.snap.webpack5 | 604 ++++ .../setup-exit-signals.test.js.snap.webpack5 | 25 + .../setup-middlewares.test.js.snap.webpack5 | 39 + .../static-directory.test.js.snap.webpack5 | 149 + .../static-public-path.test.js.snap.webpack5 | 262 ++ .../__snapshots__/stats.test.js.snap.webpack5 | 57 + .../target.test.js.snap.webpack5 | 94 + .../watch-files.test.js.snap.webpack5 | 311 ++ ...socket-communication.test.js.snap.webpack5 | 81 + ...eb-socket-server-url.test.js.snap.webpack5 | 710 +++++ .../web-socket-server.test.js.snap.webpack5 | 9 + .../rspack-dev-server/tests/e2e/port.test.js | 115 + .../tests/e2e/progress.test.js | 90 + .../tests/e2e/range-header.test.js | 111 + .../e2e/server-and-client-transport.test.js | 620 ++++ .../tests/e2e/server.test.js | 1441 +++++++++ .../tests/e2e/setup-exit-signals.test.js | 118 + .../tests/e2e/setup-middlewares.test.js | 171 + .../tests/e2e/static-directory.test.js | 686 ++++ .../tests/e2e/static-public-path.test.js | 1101 +++++++ .../rspack-dev-server/tests/e2e/stats.test.js | 147 + .../tests/e2e/target.test.js | 94 + .../tests/e2e/watch-files.test.js | 682 ++++ .../e2e/web-socket-communication.test.js | 223 ++ .../tests/e2e/web-socket-server-url.test.js | 2765 +++++++++++++++++ .../tests/e2e/web-socket-server.test.js | 68 + .../fixtures/https-certificate/ca-symlink.pem | 1 + .../tests/fixtures/https-certificate/ca.pem | 27 + .../https-certificate/server-symlink.crt | 1 + .../https-certificate/server-symlink.key | 1 + .../https-certificate/server-symlink.pfx | 1 + .../fixtures/https-certificate/server.crt | 21 + .../fixtures/https-certificate/server.key | 28 + .../fixtures/https-certificate/server.pfx | Bin 0 -> 2469 bytes .../webpack.config.js | 24 - .../fixtures/provide-plugin-default/foo.js | 9 + .../provide-plugin-default/webpack.config.js | 21 + .../provide-plugin-sockjs-config/foo.js | 9 + .../webpack.config.js | 21 + .../fixtures/provide-plugin-ws-config/foo.js | 9 + .../webpack.config.js | 21 + .../tests/fixtures/reload-config-2/foo.js | 4 + .../reload-config-2/webpack.config.js | 29 + .../tests/fixtures/reload-config/main.css | 1 + .../foo.js | 0 .../fixtures/static-config/other/foo.html | 1 + .../static-config/public/assets/example.txt | 0 .../static-config/public/assets/other.txt | 0 .../static-config/public/bar/index.html | 1 + .../fixtures/static-config/public/foo.wasm | 0 .../fixtures/static-config/public/index.html | 1 + .../public/node_modules/.gitkeep | 0 .../public/node_modules/index.html | 1 + .../fixtures/static-config/public/other.html | 1 + .../fixtures/static-config/static/index.html | 1 + .../fixtures/static-config/webpack.config.js | 14 + .../tests/fixtures/watch-files-config/foo.js | 3 + .../watch-files-config/other/foo.html | 1 + .../public/assets/example.txt | 0 .../public/assets/non-exist.txt | 1 + .../public/assets/other.txt | 0 .../watch-files-config/public/bar/index.html | 1 + .../watch-files-config/public/other.html | 1 + .../watch-files-config/static/index.html | 1 + .../watch-files-config/webpack.config.js | 17 + .../tests/helpers/conditional-test.js | 12 + .../tests/helpers/custom-http.js | 5 + .../tests/helpers/normalize-options.js | 38 + .../tests/helpers/test-server.js | 95 + 73 files changed, 11353 insertions(+), 33 deletions(-) create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/port.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/server-and-client-transport.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/server.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/setup-exit-signals.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/setup-middlewares.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/static-directory.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/static-public-path.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/stats.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/target.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/watch-files.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-communication.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server-url.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server.test.js.snap.webpack5 create mode 100644 packages/rspack-dev-server/tests/e2e/port.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/progress.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/range-header.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/server-and-client-transport.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/server.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/setup-exit-signals.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/setup-middlewares.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/static-directory.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/static-public-path.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/stats.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/target.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/watch-files.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/web-socket-communication.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js create mode 100644 packages/rspack-dev-server/tests/e2e/web-socket-server.test.js create mode 120000 packages/rspack-dev-server/tests/fixtures/https-certificate/ca-symlink.pem create mode 100644 packages/rspack-dev-server/tests/fixtures/https-certificate/ca.pem create mode 120000 packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.crt create mode 120000 packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.key create mode 120000 packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.pfx create mode 100644 packages/rspack-dev-server/tests/fixtures/https-certificate/server.crt create mode 100644 packages/rspack-dev-server/tests/fixtures/https-certificate/server.key create mode 100644 packages/rspack-dev-server/tests/fixtures/https-certificate/server.pfx delete mode 100644 packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-default/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-default/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config-2/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config-2/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/main.css rename packages/rspack-dev-server/tests/fixtures/{multi-compiler-one-configuration copy => static-config}/foo.js (100%) create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/other/foo.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/assets/example.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/assets/other.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/bar/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/foo.wasm create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/.gitkeep create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/public/other.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/static/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/static-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/foo.js create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/other/foo.html create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/example.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/non-exist.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/other.txt create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/public/bar/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/public/other.html create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/static/index.html create mode 100644 packages/rspack-dev-server/tests/fixtures/watch-files-config/webpack.config.js create mode 100644 packages/rspack-dev-server/tests/helpers/conditional-test.js create mode 100644 packages/rspack-dev-server/tests/helpers/custom-http.js create mode 100644 packages/rspack-dev-server/tests/helpers/normalize-options.js create mode 100644 packages/rspack-dev-server/tests/helpers/test-server.js diff --git a/.gitignore b/.gitignore index 78d24875a40..9a4c6d416bd 100644 --- a/.gitignore +++ b/.gitignore @@ -223,6 +223,8 @@ tests/plugin-test/css-extract/js tests/plugin-test/html-plugin/js +!packages/rspack-dev-server/tests/fixtures/**/node_modules + /webpack-examples/**/dist smoke-example diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index 5207445e398..0b27f291c61 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -13,15 +13,17 @@ const config = { testPathIgnorePatterns: isWin ? [] : [ - // TODO: check why http proxy server throw error with websocket server - "/tests/e2e/allowed-hosts.test.js", - // TODO: check why this test timeout - "/tests/e2e/host.test.js", - // TODO: not support progress plugin event yet - "/tests/e2e/progress.test.js", - // TODO: check why this test throw error when run with other tests - "/tests/e2e/watch-files.test.js" - ], + // TODO: check why http proxy server throw error with websocket server + "/tests/e2e/allowed-hosts.test.js", + // TODO: check why this test timeout + "/tests/e2e/host.test.js", + // TODO: not support progress plugin event yet + "/tests/e2e/progress.test.js", + // TODO: check why this test throw error when run with other tests + "/tests/e2e/watch-files.test.js", + // TODO: check why this test timeout + "/tests/e2e/web-socket-server-url.test.js" + ], cache: false, testTimeout: process.env.CI ? 120000 : 30000, transform: { diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/port.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/port.test.js.snap.webpack5 new file mode 100644 index 00000000000..56c9509a19d --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/port.test.js.snap.webpack5 @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`port should work using "" port : console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "" port : page errors 1`] = `[]`; + +exports[`port should work using "0" port : console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "0" port : page errors 1`] = `[]`; + +exports[`port should work using "8161" port : console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "8161" port : console messages 2`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "8161" port : page errors 1`] = `[]`; + +exports[`port should work using "8161" port : page errors 2`] = `[]`; + +exports[`port should work using "auto" port : console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "auto" port : page errors 1`] = `[]`; + +exports[`port should work using "undefined" port : console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`port should work using "undefined" port : page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/server-and-client-transport.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/server-and-client-transport.test.js.snap.webpack5 new file mode 100644 index 00000000000..6fa2cfc536f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/server-and-client-transport.test.js.snap.webpack5 @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`server and client transport should throw an error on invalid path to client transport 1`] = `"client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class "`; + +exports[`server and client transport should throw an error on invalid path to server transport 1`] = `"webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) via require.resolve(...), or the class itself which extends BaseServer"`; + +exports[`server and client transport should throw an error on wrong path 1`] = `"webSocketServer (webSocketServer.type) must be a string denoting a default implementation (e.g. 'ws', 'sockjs'), a full path to a JS file which exports a class extending BaseServer (webpack-dev-server/lib/servers/BaseServer.js) via require.resolve(...), or the class itself which extends BaseServer"`; + +exports[`server and client transport should use "sockjs" transport and "sockjs" web socket server 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "sockjs" transport, when web socket server is not specify 1`] = `[]`; + +exports[`server and client transport should use "sockjs" web socket server when specify "sockjs" value 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "sockjs" web socket server when specify "sockjs" value using object 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "ws" transport and "ws" web socket server 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "ws" transport, when web socket server is not specify 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "ws" web socket server when specify "ws" value 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use "ws" web socket server when specify "ws" value using object 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use custom transport and "sockjs" web socket server 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "open", + "hot", + "liveReload", + "reconnect", + "overlay", + "hash", + "ok", +] +`; + +exports[`server and client transport should use custom web socket server when specify class 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use custom web socket server when specify class using object 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use custom web socket server when specify path to class 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use custom web socket server when specify path to class using object 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; + +exports[`server and client transport should use default web socket server ("ws") 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", +] +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/server.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/server.test.js.snap.webpack5 new file mode 100644 index 00000000000..dabcd217098 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/server.test.js.snap.webpack5 @@ -0,0 +1,604 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`server option as object allow to pass more options should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object allow to pass more options should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "", + "cert": "", + "key": "", + "minVersion": "TLSv1.1", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": false, +} +`; + +exports[`server option as object allow to pass more options should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object allow to pass more options should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object allow to pass more options should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are array of buffers should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of buffers should handle GET request to index route (/): https options 1`] = ` +{ + "ca": [ + "", + ], + "cert": [ + "", + ], + "key": [ + "", + ], + "passphrase": "webpack-dev-server", + "pfx": [ + "", + ], + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are array of buffers should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of buffers should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are array of buffers should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are array of paths to files should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of paths to files should handle GET request to index route (/): https options 1`] = ` +{ + "ca": [ + "", + ], + "cert": [ + "", + ], + "key": [ + "", + ], + "passphrase": "webpack-dev-server", + "pfx": [ + "", + ], + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are array of paths to files should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of paths to files should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are array of paths to files should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are array of strings should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of strings should handle GET request to index route (/): https options 1`] = ` +{ + "ca": [ + "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kv +C/hf5Ei1J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYu +Dy9WkFuMie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhs +EENnH6sUE9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2sw +duxJTWRINmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+ +T8emgklStASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABAoIBAGqWKPE1QnT3T+3J +G+ITz9P0dDFbvWltlTZmeSJh/s2q+WZloUNtBxdmwbqT/1QecnkyGgyzVCjvSKsu +CgVjWNVAhysgtNtxRT4BVflffBXLVH2qsBjpsLRGU6EcMXuPGTiEp3YRHNuO6Aj8 +oP8fEsCGPc9DlJMGgxQRAKlrVF8TN/0j6Qk+YpS4MZ0YFQfBY+WdKu04Z8TVTplQ +tTkiGpBI+Oj85jF59aQiizglJgADkAZ6zmbrctm/G9jPxh7JLS2cKI0ECZgK5yAc +pk10E1YWhoCksjr9arxy6fS9TiX9P15vv06k+s7c4c5X7XDm3X0GWeSbqBMJb8q7 +BhZQNzECgYEA4kAtymDBvFYiZFq7+lzQBRKAI1RCq7YqxlieumH0PSkie2bba3dW +NVdTi7at8+GDB9/cHUPKzg/skfJllek57MZmusiVwB/Lmp/IlW8YyGShdYZ7zQsV +KMWJljpky3BEDM5sb08wIkfrOkelI/S4Bqqabd9JzOMJzoTiVOlMam8CgYEA3ctN +yonWz2bsnCUstQvQCLdI5a8Q7GJvlH2awephxGXIKGUuRmyyop0AnRnIBEWtOQV7 +yZjW32bU+Wt+2BJ247EyJiIQ4gT+T51t+v/Wt1YNbL3dSj9ttOvwYd4H2W4E7EIO +GKIF4I39FM7r8NfG7YE7S1aVcnrqs01N3nhd9HMCgYEAjepbzpmqbAxLPk97oase +AFB+d6qetz5ozklAJwDSRprKukTmVR5hwMup5/UKX/OQURwl4WVojKCIb3NwLPxC +DTbVsUuoQv6uo6qeEr3A+dHFRQa6GP9eolhl2Ql/t+wPg0jn01oEgzxBXCkceNVD +qUrR2yE4FYBD4nqPzVsZR5kCgYEA1yTi7NkQeldIpZ6Z43T18753A/Xx4JsLyWqd +uAT3mV9x7V1Yqg++qGbLtZjQoPRFt85N6ZxMsqA5b0iK3mXq1auJDdx1rAlT9z6q +9JM/YNAkbZsvEVq9vIYxw31w98T1GYhpzBM+yDhzir+9tv5YhQKa1dXDWi1JhWwz +YN45pWkCgYEAxuVsJ4D4Th5o050ppWpnxM/WuMhIUKqaoFTVucMKFzn+g24y9pv5 +miYdNYIk4Y+4pzHG6ZGZSHJcQ9BLui6H/nLQnqkgCb2lT5nfp7/GKdus7BdcjPGs +fcV46yL7/X0m8nDb3hkwwrDTU4mKFkMrzKpjdZBsttEmW0Aw/3y36gU= +-----END RSA PRIVATE KEY----- +", + ], + "cert": [ + "-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 +J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM +ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU +E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI +NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS +tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb +3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 +6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg +LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb +hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ +Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU +DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I +7Q== +-----END CERTIFICATE----- +", + ], + "key": [ + "-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt +CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK +dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF +gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k +9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy +7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ +3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 +ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU +faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 +/SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ +BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ +Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 +XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV +6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj +9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U +fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P +nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz +TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV +HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY +/16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX +JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 +zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ +iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml +amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 +Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW +QyvMqmN1kGy20SZbQDD/fLfqBQ== +-----END PRIVATE KEY----- +", + ], + "passphrase": "webpack-dev-server", + "pfx": [ + "", + ], + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are array of strings should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are array of strings should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are array of strings should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): console messages 2`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "", + "cert": "", + "key": "", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): https options 2`] = ` +{ + "ca": "", + "cert": "", + "key": "", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): page errors 2`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): response status 2`] = `200`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are buffer should handle GET request to index route (/): response text 2`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are buffer, key and pfx are objects should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer, key and pfx are objects should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "", + "cert": "", + "key": [ + { + "pem": "", + }, + ], + "passphrase": "webpack-dev-server", + "pfx": [ + { + "buf": "", + }, + ], + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are buffer, key and pfx are objects should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are buffer, key and pfx are objects should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are buffer, key and pfx are objects should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are paths to files should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are paths to files should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "", + "cert": "", + "key": "", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are paths to files should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are paths to files should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are paths to files should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are strings should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are strings should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kv +C/hf5Ei1J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYu +Dy9WkFuMie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhs +EENnH6sUE9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2sw +duxJTWRINmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+ +T8emgklStASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABAoIBAGqWKPE1QnT3T+3J +G+ITz9P0dDFbvWltlTZmeSJh/s2q+WZloUNtBxdmwbqT/1QecnkyGgyzVCjvSKsu +CgVjWNVAhysgtNtxRT4BVflffBXLVH2qsBjpsLRGU6EcMXuPGTiEp3YRHNuO6Aj8 +oP8fEsCGPc9DlJMGgxQRAKlrVF8TN/0j6Qk+YpS4MZ0YFQfBY+WdKu04Z8TVTplQ +tTkiGpBI+Oj85jF59aQiizglJgADkAZ6zmbrctm/G9jPxh7JLS2cKI0ECZgK5yAc +pk10E1YWhoCksjr9arxy6fS9TiX9P15vv06k+s7c4c5X7XDm3X0GWeSbqBMJb8q7 +BhZQNzECgYEA4kAtymDBvFYiZFq7+lzQBRKAI1RCq7YqxlieumH0PSkie2bba3dW +NVdTi7at8+GDB9/cHUPKzg/skfJllek57MZmusiVwB/Lmp/IlW8YyGShdYZ7zQsV +KMWJljpky3BEDM5sb08wIkfrOkelI/S4Bqqabd9JzOMJzoTiVOlMam8CgYEA3ctN +yonWz2bsnCUstQvQCLdI5a8Q7GJvlH2awephxGXIKGUuRmyyop0AnRnIBEWtOQV7 +yZjW32bU+Wt+2BJ247EyJiIQ4gT+T51t+v/Wt1YNbL3dSj9ttOvwYd4H2W4E7EIO +GKIF4I39FM7r8NfG7YE7S1aVcnrqs01N3nhd9HMCgYEAjepbzpmqbAxLPk97oase +AFB+d6qetz5ozklAJwDSRprKukTmVR5hwMup5/UKX/OQURwl4WVojKCIb3NwLPxC +DTbVsUuoQv6uo6qeEr3A+dHFRQa6GP9eolhl2Ql/t+wPg0jn01oEgzxBXCkceNVD +qUrR2yE4FYBD4nqPzVsZR5kCgYEA1yTi7NkQeldIpZ6Z43T18753A/Xx4JsLyWqd +uAT3mV9x7V1Yqg++qGbLtZjQoPRFt85N6ZxMsqA5b0iK3mXq1auJDdx1rAlT9z6q +9JM/YNAkbZsvEVq9vIYxw31w98T1GYhpzBM+yDhzir+9tv5YhQKa1dXDWi1JhWwz +YN45pWkCgYEAxuVsJ4D4Th5o050ppWpnxM/WuMhIUKqaoFTVucMKFzn+g24y9pv5 +miYdNYIk4Y+4pzHG6ZGZSHJcQ9BLui6H/nLQnqkgCb2lT5nfp7/GKdus7BdcjPGs +fcV46yL7/X0m8nDb3hkwwrDTU4mKFkMrzKpjdZBsttEmW0Aw/3y36gU= +-----END RSA PRIVATE KEY----- +", + "cert": "-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 +J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM +ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU +E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI +NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS +tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb +3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 +6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg +LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb +hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ +Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU +DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I +7Q== +-----END CERTIFICATE----- +", + "key": "-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt +CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK +dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF +gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k +9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy +7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ +3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 +ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU +faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 +/SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ +BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ +Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 +XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV +6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj +9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U +fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P +nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz +TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV +HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY +/16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX +JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 +zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ +iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml +amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 +Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW +QyvMqmN1kGy20SZbQDD/fLfqBQ== +-----END PRIVATE KEY----- +", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are strings should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are strings should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are strings should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object ca, pfx, key and cert are strings, key and pfx are objects should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are strings, key and pfx are objects should handle GET request to index route (/): https options 1`] = ` +{ + "ca": "-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kv +C/hf5Ei1J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYu +Dy9WkFuMie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhs +EENnH6sUE9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2sw +duxJTWRINmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+ +T8emgklStASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABAoIBAGqWKPE1QnT3T+3J +G+ITz9P0dDFbvWltlTZmeSJh/s2q+WZloUNtBxdmwbqT/1QecnkyGgyzVCjvSKsu +CgVjWNVAhysgtNtxRT4BVflffBXLVH2qsBjpsLRGU6EcMXuPGTiEp3YRHNuO6Aj8 +oP8fEsCGPc9DlJMGgxQRAKlrVF8TN/0j6Qk+YpS4MZ0YFQfBY+WdKu04Z8TVTplQ +tTkiGpBI+Oj85jF59aQiizglJgADkAZ6zmbrctm/G9jPxh7JLS2cKI0ECZgK5yAc +pk10E1YWhoCksjr9arxy6fS9TiX9P15vv06k+s7c4c5X7XDm3X0GWeSbqBMJb8q7 +BhZQNzECgYEA4kAtymDBvFYiZFq7+lzQBRKAI1RCq7YqxlieumH0PSkie2bba3dW +NVdTi7at8+GDB9/cHUPKzg/skfJllek57MZmusiVwB/Lmp/IlW8YyGShdYZ7zQsV +KMWJljpky3BEDM5sb08wIkfrOkelI/S4Bqqabd9JzOMJzoTiVOlMam8CgYEA3ctN +yonWz2bsnCUstQvQCLdI5a8Q7GJvlH2awephxGXIKGUuRmyyop0AnRnIBEWtOQV7 +yZjW32bU+Wt+2BJ247EyJiIQ4gT+T51t+v/Wt1YNbL3dSj9ttOvwYd4H2W4E7EIO +GKIF4I39FM7r8NfG7YE7S1aVcnrqs01N3nhd9HMCgYEAjepbzpmqbAxLPk97oase +AFB+d6qetz5ozklAJwDSRprKukTmVR5hwMup5/UKX/OQURwl4WVojKCIb3NwLPxC +DTbVsUuoQv6uo6qeEr3A+dHFRQa6GP9eolhl2Ql/t+wPg0jn01oEgzxBXCkceNVD +qUrR2yE4FYBD4nqPzVsZR5kCgYEA1yTi7NkQeldIpZ6Z43T18753A/Xx4JsLyWqd +uAT3mV9x7V1Yqg++qGbLtZjQoPRFt85N6ZxMsqA5b0iK3mXq1auJDdx1rAlT9z6q +9JM/YNAkbZsvEVq9vIYxw31w98T1GYhpzBM+yDhzir+9tv5YhQKa1dXDWi1JhWwz +YN45pWkCgYEAxuVsJ4D4Th5o050ppWpnxM/WuMhIUKqaoFTVucMKFzn+g24y9pv5 +miYdNYIk4Y+4pzHG6ZGZSHJcQ9BLui6H/nLQnqkgCb2lT5nfp7/GKdus7BdcjPGs +fcV46yL7/X0m8nDb3hkwwrDTU4mKFkMrzKpjdZBsttEmW0Aw/3y36gU= +-----END RSA PRIVATE KEY----- +", + "cert": "-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 +J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM +ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU +E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI +NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS +tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb +3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 +6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg +LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb +hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ +Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU +DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I +7Q== +-----END CERTIFICATE----- +", + "key": [ + { + "pem": "-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt +CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK +dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF +gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k +9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy +7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ +3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 +ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU +faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 +/SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ +BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ +Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 +XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV +6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj +9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U +fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P +nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz +TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV +HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY +/16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX +JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 +zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ +iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml +amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 +Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW +QyvMqmN1kGy20SZbQDD/fLfqBQ== +-----END PRIVATE KEY----- +", + }, + ], + "passphrase": "webpack-dev-server", + "pfx": [ + { + "buf": "", + }, + ], + "requestCert": false, +} +`; + +exports[`server option as object ca, pfx, key and cert are strings, key and pfx are objects should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object ca, pfx, key and cert are strings, key and pfx are objects should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object ca, pfx, key and cert are strings, key and pfx are objects should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object custom server with options should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object custom server with options should handle GET request to index route (/): http options 1`] = ` +{ + "maxHeaderSize": 16384, +} +`; + +exports[`server option as object custom server with options should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object custom server with options should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object custom server with options should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object should support the "requestCert" option should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object should support the "requestCert" option should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as object should support the "requestCert" option should pass options to the 'https.createServer' method: https options 1`] = ` +{ + "cert": "", + "key": "", + "passphrase": "webpack-dev-server", + "pfx": "", + "requestCert": true, +} +`; + +exports[`server option as object spdy server with options should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as object spdy server with options should handle GET request to index route (/): https options 1`] = ` +{ + "ca": [ + "", + ], + "cert": [ + "", + ], + "key": [ + "", + ], + "passphrase": "webpack-dev-server", + "pfx": [ + "", + ], + "requestCert": false, + "spdy": { + "protocols": [ + "h2", + "http/1.1", + ], + }, +} +`; + +exports[`server option as object spdy server with options should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as object spdy server with options should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as object spdy server with options should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as string custom-http should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as string custom-http should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as string custom-http should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as string custom-http should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as string http should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as string http should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as string http should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as string http should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as string https should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as string https should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as string https should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as string https should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; + +exports[`server option as string spdy should handle GET request to index route (/): console messages 1`] = `[]`; + +exports[`server option as string spdy should handle GET request to index route (/): page errors 1`] = `[]`; + +exports[`server option as string spdy should handle GET request to index route (/): response status 1`] = `200`; + +exports[`server option as string spdy should handle GET request to index route (/): response text 1`] = ` +"Heyo. +" +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-exit-signals.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-exit-signals.test.js.snap.webpack5 new file mode 100644 index 00000000000..79ceb12e6ed --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-exit-signals.test.js.snap.webpack5 @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGINT: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGINT: page errors 1`] = `[]`; + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGINT: response status 1`] = `200`; + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGTERM: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGTERM: page errors 1`] = `[]`; + +exports[`setupExitSignals option should handle 'SIGINT' and 'SIGTERM' signals should close and exit on SIGTERM: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-middlewares.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-middlewares.test.js.snap.webpack5 new file mode 100644 index 00000000000..c22699072a3 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/setup-middlewares.test.js.snap.webpack5 @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: console messages 1`] = `[]`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: page errors 1`] = `[]`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response headers content-type 2`] = `"text/html; charset=utf-8"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response headers content-type 3`] = `"text/html; charset=utf-8"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response headers content-type 4`] = `"text/html; charset=utf-8"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response status 1`] = `200`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response status 2`] = `200`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response status 3`] = `200`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response status 4`] = `200`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response text 1`] = `"setup-middlewares option GET"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response text 2`] = `"Hello World with path!"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response text 3`] = `"Hello World without path!"`; + +exports[`setupMiddlewares option should handle GET request to /setup-middleware/some/path route: response text 4`] = `"Hello World as function!"`; + +exports[`setupMiddlewares option should handle POST request to /setup-middleware/some/path route: console messages 1`] = `[]`; + +exports[`setupMiddlewares option should handle POST request to /setup-middleware/some/path route: page errors 1`] = `[]`; + +exports[`setupMiddlewares option should handle POST request to /setup-middleware/some/path route: response headers content-type 1`] = `"text/html; charset=utf-8"`; + +exports[`setupMiddlewares option should handle POST request to /setup-middleware/some/path route: response status 1`] = `200`; + +exports[`setupMiddlewares option should handle POST request to /setup-middleware/some/path route: response text 1`] = `"setup-middlewares option POST"`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/static-directory.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/static-directory.test.js.snap.webpack5 new file mode 100644 index 00000000000..174cb1de42e --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/static-directory.test.js.snap.webpack5 @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`static.directory option defaults to PWD should handle request to /index.html: console messages 1`] = `[]`; + +exports[`static.directory option defaults to PWD should handle request to /index.html: page errors 1`] = `[]`; + +exports[`static.directory option defaults to PWD should handle request to /index.html: response status 1`] = `200`; + +exports[`static.directory option defaults to PWD should handle request to /index.html: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.directory option disabled should not handle request to /other.html (404): console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.directory option disabled should not handle request to /other.html (404): page errors 1`] = `[]`; + +exports[`static.directory option disabled should not handle request to /other.html (404): response status 1`] = `404`; + +exports[`static.directory option disabled should not handle request to /other.html (404): response text 1`] = ` +" + + + +Error + + +
Cannot GET /index.html
+ + +" +`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should list the files inside the assets folder (200): console messages 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should list the files inside the assets folder (200): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should list the files inside the assets folder (200): response status 1`] = `200`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.directory option test listing files in folders without index.html using the default static.serveIndex option (true) should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should not list the files inside the assets folder (404): console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should not list the files inside the assets folder (404): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should not list the files inside the assets folder (404): response status 1`] = `404`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should not list the files inside the assets folder (404): response text 1`] = ` +" + + + +Error + + +
Cannot GET /assets/
+ + +" +`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): console messages 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): response status 1`] = `200`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.directory option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.directory option to directory should handle request to index route: console messages 1`] = `[]`; + +exports[`static.directory option to directory should handle request to index route: page errors 1`] = `[]`; + +exports[`static.directory option to directory should handle request to index route: response status 1`] = `200`; + +exports[`static.directory option to directory should handle request to index route: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.directory option to directory should handle request to other file: console messages 1`] = `[]`; + +exports[`static.directory option to directory should handle request to other file: page errors 1`] = `[]`; + +exports[`static.directory option to directory should handle request to other file: response status 1`] = `200`; + +exports[`static.directory option to directory should handle request to other file: response text 1`] = ` +"Other html +" +`; + +exports[`static.directory option to multiple directories should handle request first directory: console messages 1`] = `[]`; + +exports[`static.directory option to multiple directories should handle request first directory: page errors 1`] = `[]`; + +exports[`static.directory option to multiple directories should handle request first directory: response status 1`] = `200`; + +exports[`static.directory option to multiple directories should handle request first directory: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.directory option to multiple directories should handle request to second directory: console messages 1`] = `[]`; + +exports[`static.directory option to multiple directories should handle request to second directory: page errors 1`] = `[]`; + +exports[`static.directory option to multiple directories should handle request to second directory: response status 1`] = `200`; + +exports[`static.directory option to multiple directories should handle request to second directory: response text 1`] = ` +"Foo! +" +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/static-public-path.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/static-public-path.test.js.snap.webpack5 new file mode 100644 index 00000000000..3140335f472 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/static-public-path.test.js.snap.webpack5 @@ -0,0 +1,262 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`static.publicPath option Content type should handle request to example.txt: console messages 1`] = `[]`; + +exports[`static.publicPath option Content type should handle request to example.txt: page errors 1`] = `[]`; + +exports[`static.publicPath option Content type should handle request to example.txt: response header content-type 1`] = `"text/plain; charset=UTF-8"`; + +exports[`static.publicPath option Content type should handle request to example.txt: response status 1`] = `200`; + +exports[`static.publicPath option defaults to CWD should handle request to page: console messages 1`] = `[]`; + +exports[`static.publicPath option defaults to CWD should handle request to page: page errors 1`] = `[]`; + +exports[`static.publicPath option defaults to CWD should handle request to page: response status 1`] = `200`; + +exports[`static.publicPath option defaults to CWD should handle request to page: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the /foo route of second path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the /foo route of second path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the /foo route of second path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the /foo route of second path: response text 1`] = ` +"Foo! +" +`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the index of first path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the index of first path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the index of first path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the index of first path: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the other file of first path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the other file of first path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the other file of first path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries should handle request to the other file of first path: response text 1`] = ` +"Other html +" +`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of first path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of first path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of first path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of first path: response text 1`] = ` +"Foo! +" +`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of second path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of second path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of second path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the /foo route of second path: response text 1`] = ` +"Foo! +" +`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the index of first path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the index of first path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the index of first path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the index of first path: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the other file of first path: console messages 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the other file of first path: page errors 1`] = `[]`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the other file of first path: response status 1`] = `200`; + +exports[`static.publicPath option multiple static.publicPath entries with publicPath array should handle request to the other file of first path: response text 1`] = ` +"Other html +" +`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle GET request: console messages 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle GET request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle GET request: response status 1`] = `200`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle HEAD request: console messages 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle HEAD request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should handle HEAD request: response status 1`] = `200`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle DELETE request: console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle DELETE request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle DELETE request: response status 1`] = `404`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PATCH request: console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PATCH request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PATCH request: response status 1`] = `404`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle POST request: console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle POST request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle POST request: response status 1`] = `404`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PUT request: console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PUT request: page errors 1`] = `[]`; + +exports[`static.publicPath option should ignore methods other than GET and HEAD should not handle PUT request: response status 1`] = `404`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should list the files inside the assets folder (200): console messages 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should list the files inside the assets folder (200): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should list the files inside the assets folder (200): response status 1`] = `200`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex default (true) should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false shouldn't list the files inside the assets folder (404): console messages 1`] = ` +[ + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false shouldn't list the files inside the assets folder (404): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false shouldn't list the files inside the assets folder (404): response status 1`] = `404`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: false shouldn't list the files inside the assets folder (404): response text 1`] = ` +" + + + +Error + + +
Cannot GET /serve-content-at-this-url/assets/
+ + +" +`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): console messages 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should list the files inside the assets folder (200): response status 1`] = `200`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): console messages 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): page errors 1`] = `[]`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): response status 1`] = `200`; + +exports[`static.publicPath option test listing files in folders without index.html using the option static.serveIndex: true should show Heyo. because bar has index.html inside it (200): response text 1`] = ` +"Heyo +" +`; + +exports[`static.publicPath option to directory should handle request to index: console messages 1`] = `[]`; + +exports[`static.publicPath option to directory should handle request to index: page errors 1`] = `[]`; + +exports[`static.publicPath option to directory should handle request to index: response status 1`] = `200`; + +exports[`static.publicPath option to directory should handle request to index: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.publicPath option to directory should handle request to other file: console messages 1`] = `[]`; + +exports[`static.publicPath option to directory should handle request to other file: page errors 1`] = `[]`; + +exports[`static.publicPath option to directory should handle request to other file: response status 1`] = `200`; + +exports[`static.publicPath option to directory should handle request to other file: response text 1`] = ` +"Other html +" +`; + +exports[`static.publicPath option to multiple directories should handle request to first directory: console messages 1`] = `[]`; + +exports[`static.publicPath option to multiple directories should handle request to first directory: page errors 1`] = `[]`; + +exports[`static.publicPath option to multiple directories should handle request to first directory: response status 1`] = `200`; + +exports[`static.publicPath option to multiple directories should handle request to first directory: response text 1`] = ` +"Heyo. +" +`; + +exports[`static.publicPath option to multiple directories should handle request to second directory: console messages 1`] = `[]`; + +exports[`static.publicPath option to multiple directories should handle request to second directory: page errors 1`] = `[]`; + +exports[`static.publicPath option to multiple directories should handle request to second directory: response status 1`] = `200`; + +exports[`static.publicPath option to multiple directories should handle request to second directory: response text 1`] = ` +"Foo! +" +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/stats.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/stats.test.js.snap.webpack5 new file mode 100644 index 00000000000..7d40670ec9e --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/stats.test.js.snap.webpack5 @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`stats should work and respect the "ignoreWarnings" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work using "{ assets: false }" value for the "stats" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work using "{}" value for the "stats" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work using "errors-only" value for the "stats" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work using "false" value for the "stats" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work using "undefined" value for the "stats" option 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`stats should work when "stats" is not specified 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/target.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/target.test.js.snap.webpack5 new file mode 100644 index 00000000000..877b6a1e767 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/target.test.js.snap.webpack5 @@ -0,0 +1,94 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`target should work using "async-node" target: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "browserslist:defaults" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "electron-main" target: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "electron-preload" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "electron-renderer" target: console messages 1`] = `[]`; + +exports[`target should work using "es5" target: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "false" target: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "node" target: console messages 1`] = ` +[ + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "node-webkit" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "nwjs" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "web" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "web,es5" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`target should work using "webworker" target: console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/watch-files.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/watch-files.test.js.snap.webpack5 new file mode 100644 index 00000000000..5c63e7b714d --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/watch-files.test.js.snap.webpack5 @@ -0,0 +1,311 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`watchFiles option should not crash if file doesn't exist should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should not crash if file doesn't exist should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should not crash if file doesn't exist should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with array config should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with array config should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with array config should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with object with multiple paths should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with object with multiple paths should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with object with multiple paths should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with object with single path should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with object with single path should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with object with single path should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"interval":400,"poll":200} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 400, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"interval":400,"poll":200} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"interval":400,"poll":200} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"interval":400,"poll":200} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"poll":200} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 200, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"poll":200} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"poll":200} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"poll":200} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"poll":true} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": undefined, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"poll":true} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"poll":true} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"poll":true} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":false,"interval":200,"poll":400} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 200, + "persistent": true, + "usePolling": false, +} +`; + +exports[`watchFiles option should work with options {"usePolling":false,"interval":200,"poll":400} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":false,"interval":200,"poll":400} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":false,"interval":200,"poll":400} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":200} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 200, + "persistent": true, + "usePolling": false, +} +`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":200} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":200} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":200} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":true} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": undefined, + "persistent": true, + "usePolling": false, +} +`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":true} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":true} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":false,"poll":true} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":false} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": undefined, + "persistent": true, + "usePolling": false, +} +`; + +exports[`watchFiles option should work with options {"usePolling":false} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":false} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":false} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":true,"interval":200,"poll":400} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 200, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"usePolling":true,"interval":200,"poll":400} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":true,"interval":200,"poll":400} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":true,"interval":200,"poll":400} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":true,"poll":200} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": 200, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"usePolling":true,"poll":200} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":true,"poll":200} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":true,"poll":200} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {"usePolling":true} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": undefined, + "interval": undefined, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {"usePolling":true} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {"usePolling":true} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {"usePolling":true} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with string and glob should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with string and glob should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with string and glob should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with string and path to directory should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with string and path to directory should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with string and path to directory should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with string and path to file should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with string and path to file should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with string and path to file should reload when file content is changed: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-communication.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-communication.test.js.snap.webpack5 new file mode 100644 index 00000000000..6937debfff0 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-communication.test.js.snap.webpack5 @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web socket communication should work and close web socket client connection when web socket server closed ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`web socket communication should work and close web socket client connection when web socket server closed ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket communication should work and close web socket client connection when web socket server closed ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", +] +`; + +exports[`web socket communication should work and close web socket client connection when web socket server closed ("ws"): page errors 1`] = `[]`; + +exports[`web socket communication should work and reconnect when the connection is lost ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "Failed to load resource: the server responded with a status of 404 (Not Found)", + "[HMR] Cannot find update. Need to do a full reload!", + "[HMR] (Probably because of restarting the webpack-dev-server)", + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`web socket communication should work and reconnect when the connection is lost ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket communication should work and reconnect when the connection is lost ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "[webpack-dev-server] Disconnected!", + "[webpack-dev-server] Trying to reconnect...", + "[webpack-dev-server] App hot update...", + "[HMR] Checking for updates on the server...", + "Failed to load resource: the server responded with a status of 404 (Not Found)", + "[HMR] Cannot find update. Need to do a full reload!", + "[HMR] (Probably because of restarting the webpack-dev-server)", + "Failed to load resource: the server responded with a status of 404 (Not Found)", +] +`; + +exports[`web socket communication should work and reconnect when the connection is lost ("ws"): page errors 1`] = `[]`; + +exports[`web socket communication should work and terminate client that is not alive ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket communication should work and terminate client that is not alive ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket communication should work and terminate client that is not alive ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket communication should work and terminate client that is not alive ("ws"): page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server-url.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server-url.test.js.snap.webpack5 new file mode 100644 index 00000000000..68160182e3f --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server-url.test.js.snap.webpack5 @@ -0,0 +1,710 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web socket server URL should not work and output disconnect wrong web socket URL ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "Failed to load resource: net::ERR_NAME_NOT_RESOLVED", + "[webpack-dev-server] Disconnected!", +] +`; + +exports[`web socket server URL should not work and output disconnect wrong web socket URL ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should not work and output disconnect wrong web socket URL ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", + "WebSocket connection to 'ws://unknown.unknown:/unknown' failed: Error in connection establishment: net::ERR_NAME_NOT_RESOLVED", + "[webpack-dev-server] JSHandle@object", + "[webpack-dev-server] Disconnected!", +] +`; + +exports[`web socket server URL should not work and output disconnect wrong web socket URL ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work and throw an error on invalid web socket URL ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", +] +`; + +exports[`web socket server URL should work and throw an error on invalid web socket URL ("sockjs"): page errors 1`] = ` +[ + "The URL's scheme must be either 'http:' or 'https:'. 'unknown:' is not allowed.", +] +`; + +exports[`web socket server URL should work and throw an error on invalid web socket URL ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", +] +`; + +exports[`web socket server URL should work and throw an error on invalid web socket URL ("ws"): page errors 1`] = ` +[ + "SyntaxError: Failed to construct 'WebSocket': The URL's scheme must be either 'http', 'https', 'ws', or 'wss'. 'unknown' is not allowed.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are different ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are different ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are different ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are different ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are same ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are same ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are same ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are different and ports are same ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when hostnames are same and ports are different ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are same and ports are different ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when hostnames are same and ports are different ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when hostnames are same and ports are different ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when the "host" option is "local-ip" and the "port" option is "auto" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when the "host" option is "local-ip" and the "port" option is "auto" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work behind proxy, when the "host" option is "local-ip" and the "port" option is "auto" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work behind proxy, when the "host" option is "local-ip" and the "port" option is "auto" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is "local-ip" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is "local-ip" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is "local-ip" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is "local-ip" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is "local-ipv4" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is "local-ipv4" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is "local-ipv4" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is "local-ipv4" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is IPv4 ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is IPv4 ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "host" option is IPv4 ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "host" option is IPv4 ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "port" option is "auto" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "port" option is "auto" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work when "port" option is "auto" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work when "port" option is "auto" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "client.webSocketURL.*" options ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "client.webSocketURL.*" options ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "client.webSocketURL.*" options ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "client.webSocketURL.*" options ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "server: 'https'" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "server: 'https'" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "server: 'https'" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "server: 'https'" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "server: 'spdy'" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "server: 'spdy'" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with "server: 'spdy'" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with "server: 'spdy'" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with default "/ws" value of the "client.webSocketURL.pathname" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with default "/ws" value of the "client.webSocketURL.pathname" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with default "/ws" value of the "client.webSocketURL.pathname" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with default "/ws" value of the "client.webSocketURL.pathname" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL" option as "string" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL" option as "string" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL" option as "string" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL" option as "string" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option using "0.0.0.0" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option using "0.0.0.0" value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option using "0.0.0.0" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.host" option using "0.0.0.0" value ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.password" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.password" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.password" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.password" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending with slash ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending with slash ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending with slash ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending with slash ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending without slash ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending without slash ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending without slash ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending without slash ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" using empty value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" using empty value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" using empty value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" using empty value ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "prefix" for compatibility with "sockjs" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "prefix" for compatibility with "sockjs" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "prefix" for compatibility with "sockjs" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.pathname" option and the custom web socket server "prefix" for compatibility with "sockjs" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option as string ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option as string ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option as string ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option as string ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option using "0" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option using "0" value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option using "0" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.port" option using "0" value ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "auto:" value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "auto:" value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "auto:" value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "auto:" value ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws:" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws:" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws:" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws:" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" and "client.webSocketURL.password" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" and "client.webSocketURL.password" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" and "client.webSocketURL.password" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" and "client.webSocketURL.password" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" option ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" option ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" option ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the "client.webSocketURL.username" option ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the custom web socket server "path" ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the custom web socket server "path" ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the custom web socket server "path" ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the custom web socket server "path" ("ws"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the custom web socket server "path" using empty value ("sockjs"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the custom web socket server "path" using empty value ("sockjs"): page errors 1`] = `[]`; + +exports[`web socket server URL should work with the custom web socket server "path" using empty value ("ws"): console messages 1`] = ` +[ + "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", + "[HMR] Waiting for update signal from WDS...", + "Hey.", +] +`; + +exports[`web socket server URL should work with the custom web socket server "path" using empty value ("ws"): page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server.test.js.snap.webpack5 new file mode 100644 index 00000000000..2efd0f39195 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/web-socket-server.test.js.snap.webpack5 @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`web socket server should work allow to disable: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`web socket server should work allow to disable: page errors 1`] = `[]`; diff --git a/packages/rspack-dev-server/tests/e2e/port.test.js b/packages/rspack-dev-server/tests/e2e/port.test.js new file mode 100644 index 00000000000..bebc3c8f6b3 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/port.test.js @@ -0,0 +1,115 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").port; + +describe("port", () => { + const ports = [ + "", + // eslint-disable-next-line no-undefined + undefined, + "auto", + port, + `${port}`, + 0, + "-1", + "99999" + ]; + + for (const testedPort of ports) { + it(`should work using "${testedPort}" port `, async () => { + const compiler = webpack(config); + const devServerOptions = {}; + + let usedPort; + + if ( + testedPort === "" || + typeof testedPort === "undefined" + ) { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = port; + usedPort = port; + } else if (testedPort === "auto") { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = port; + devServerOptions.port = testedPort; + usedPort = port; + } else { + devServerOptions.port = testedPort; + usedPort = testedPort; + } + + const server = new Server(devServerOptions, compiler); + + let errored; + + try { + await server.start(); + } catch (error) { + errored = error; + } + + if (testedPort === "-1" || testedPort === "99999") { + const errorMessageRegExp = new RegExp( + `options.port should be >= 0 and < 65536` + ); + + try { + expect(errored.message).toMatch(errorMessageRegExp); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + return; + } + + const address = server.server.address(); + + if (testedPort === 0) { + expect(typeof address.port).toBe("number"); + } else { + expect(address.port).toBe(Number(usedPort)); + } + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${address.port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + + if ( + testedPort === "" || + typeof testedPort === "undefined" + ) { + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + } + }); + } +}); diff --git a/packages/rspack-dev-server/tests/e2e/progress.test.js b/packages/rspack-dev-server/tests/e2e/progress.test.js new file mode 100644 index 00000000000..87017a55167 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/progress.test.js @@ -0,0 +1,90 @@ +"use strict"; + +const path = require("path"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const reloadConfig = require("../fixtures/reload-config-2/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").progress; + +const cssFilePath = path.resolve( + __dirname, + "../fixtures/reload-config-2/main.css" +); + +describe("progress", () => { + it("should work and log progress in a browser console", async () => { + fs.writeFileSync(cssFilePath, "body { background-color: rgb(0, 0, 255); }"); + + const compiler = webpack(reloadConfig); + const devServerOptions = { + port, + client: { + progress: true + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + try { + const { page, browser } = await runBrowser(); + + const consoleMessages = []; + + try { + let doHotUpdate = false; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + if (/\.hot-update\.(json|js)$/.test(interceptedRequest.url())) { + doHotUpdate = true; + } + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + fs.writeFileSync( + cssFilePath, + "body { background-color: rgb(255, 0, 0); }" + ); + + await new Promise(resolve => { + const timer = setInterval(() => { + if (doHotUpdate) { + clearInterval(timer); + + resolve(); + } + }, 100); + }); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + + const progressConsoleMessage = consoleMessages.filter(message => + /^\[webpack-dev-server\] (\[[a-zA-Z]+\] )?[0-9]{1,3}% - /.test( + message.text() + ) + ); + + expect(progressConsoleMessage.length > 0).toBe(true); + } catch (error) { + throw error; + } finally { + fs.unlinkSync(cssFilePath); + + await server.stop(); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/range-header.test.js b/packages/rspack-dev-server/tests/e2e/range-header.test.js new file mode 100644 index 00000000000..7679cecd0fa --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/range-header.test.js @@ -0,0 +1,111 @@ +"use strict"; + +const request = require("supertest"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/static-config/webpack.config"); +const port = require("../helpers/ports-map")["range-header"]; + +describe("'Range' header", () => { + let compiler; + let server; + + beforeAll(async () => { + compiler = webpack(config); + + server = new Server({ port }, compiler); + + await server.start(); + }); + + afterAll(async () => { + await server.stop(); + }); + + it('should work with "Range" header using "GET" method', async () => { + const response = await request(server.app).get("/main.js"); + + expect(response.status).toBe(200); + expect(response.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(response.headers["accept-ranges"]).toBe("bytes"); + + const responseContent = response.text; + const responseRange = await request(server.app) + .get("/main.js") + .set("Range", "bytes=0-499"); + + expect(responseRange.status).toBe(206); + expect(responseRange.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(responseRange.headers["content-length"]).toBe("500"); + expect(responseRange.headers["content-range"]).toMatch(/^bytes 0-499\//); + expect(responseRange.text).toBe(responseContent.slice(0, 500)); + expect(responseRange.text.length).toBe(500); + }); + + it('should work with "Range" header using "HEAD" method', async () => { + const response = await request(server.app).head("/main.js"); + + expect(response.status).toBe(200); + expect(response.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(response.headers["accept-ranges"]).toBe("bytes"); + + const responseRange = await request(server.app) + .head("/main.js") + .set("Range", "bytes=0-499"); + + expect(responseRange.status).toBe(206); + expect(responseRange.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(responseRange.headers["content-length"]).toBe("500"); + expect(responseRange.headers["content-range"]).toMatch(/^bytes 0-499\//); + }); + + it('should work with unsatisfiable "Range" header using "GET" method', async () => { + const response = await request(server.app).get("/main.js"); + + expect(response.status).toBe(200); + expect(response.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(response.headers["accept-ranges"]).toBe("bytes"); + + const responseRange = await request(server.app) + .get("/main.js") + .set("Range", "bytes=99999999999-"); + + expect(responseRange.status).toBe(416); + expect(responseRange.headers["content-type"]).toBe( + "text/html; charset=utf-8" + ); + expect(responseRange.headers["content-range"]).toMatch(/^bytes \*\//); + }); + + it('should work with malformed "Range" header using "GET" method', async () => { + const response = await request(server.app).get("/main.js"); + + expect(response.status).toBe(200); + expect(response.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(response.headers["accept-ranges"]).toBe("bytes"); + + const responseContent = response.text; + const responseRange = await request(server.app) + .get("/main.js") + .set("Range", "bytes"); + + expect(responseRange.status).toBe(200); + expect(responseRange.headers["content-type"]).toBe( + "application/javascript; charset=utf-8" + ); + expect(responseRange.text).toBe(responseContent); + expect(responseRange.text.length).toBe(responseContent.length); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/server-and-client-transport.test.js b/packages/rspack-dev-server/tests/e2e/server-and-client-transport.test.js new file mode 100644 index 00000000000..25dde36936c --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/server-and-client-transport.test.js @@ -0,0 +1,620 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const WebsocketServer = require("webpack-dev-server/lib/servers/WebsocketServer"); +const defaultConfig = require("../fixtures/provide-plugin-default/webpack.config"); +const sockjsConfig = require("../fixtures/provide-plugin-sockjs-config/webpack.config"); +const wsConfig = require("../fixtures/provide-plugin-ws-config/webpack.config"); +const customConfig = require("../fixtures/provide-plugin-custom/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["server-and-client-transport"]; + +describe("server and client transport", () => { + it('should use default web socket server ("ws")', async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "ws" web socket server when specify "ws" value', async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + webSocketServer: "ws" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "ws" web socket server when specify "ws" value using object', async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + webSocketServer: { + type: "ws" + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "sockjs" web socket server when specify "sockjs" value', async () => { + const compiler = webpack(sockjsConfig); + const devServerOptions = { + port, + webSocketServer: "sockjs" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "sockjs" web socket server when specify "sockjs" value using object', async () => { + const compiler = webpack(sockjsConfig); + const devServerOptions = { + port, + webSocketServer: { + type: "sockjs" + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should use custom web socket server when specify class", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + }, + webSocketServer: WebsocketServer + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should use custom web socket server when specify class using object", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + }, + webSocketServer: { + type: WebsocketServer + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should use custom web socket server when specify path to class", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + }, + webSocketServer: require.resolve( + "webpack-dev-server/lib/servers/WebsocketServer" + ) + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should use custom web socket server when specify path to class using object", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + }, + webSocketServer: { + type: require.resolve("webpack-dev-server/lib/servers/WebsocketServer") + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should throw an error on wrong path", async () => { + expect.assertions(1); + + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + webSocketServer: { + type: "/bad/path/to/implementation" + } + }; + const server = new Server(devServerOptions, compiler); + + try { + await server.start(); + } catch (error) { + expect(error.message).toMatchSnapshot(); + } finally { + await server.stop(); + } + }); + + it('should use "sockjs" transport, when web socket server is not specify', async () => { + const compiler = webpack(sockjsConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "sockjs" + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/main.js`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "ws" transport, when web socket server is not specify', async () => { + const compiler = webpack(wsConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + } + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "sockjs" transport and "sockjs" web socket server', async () => { + const compiler = webpack(sockjsConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "sockjs" + }, + webSocketServer: "sockjs" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use "ws" transport and "ws" web socket server', async () => { + const compiler = webpack(wsConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "ws" + }, + webSocketServer: "ws" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it('should use custom transport and "sockjs" web socket server', async () => { + const compiler = webpack(customConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: require.resolve( + "../fixtures/custom-client/CustomSockJSClient" + ) + }, + webSocketServer: "sockjs" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + const isCorrectTransport = await page.evaluate( + () => window.injectedClient === window.expectedClient + ); + + expect(isCorrectTransport).toBe(true); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it("should throw an error on invalid path to server transport", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + webSocketServer: { + type: "invalid/path" + } + }; + const server = new Server(devServerOptions, compiler); + await expect(async () => { + await server.start(); + }).rejects.toThrowErrorMatchingSnapshot(); + + await server.stop(); + }); + + it("should throw an error on invalid path to client transport", async () => { + const compiler = webpack(defaultConfig); + const devServerOptions = { + port, + client: { + webSocketTransport: "invalid/path" + } + }; + const server = new Server(devServerOptions, compiler); + await expect(async () => { + await server.start(); + }).rejects.toThrowErrorMatchingSnapshot(); + + await server.stop(); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/server.test.js b/packages/rspack-dev-server/tests/e2e/server.test.js new file mode 100644 index 00000000000..ed6ef655495 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/server.test.js @@ -0,0 +1,1441 @@ +"use strict"; + +const https = require("https"); +const path = require("path"); +const fs = require("graceful-fs"); +const request = require("supertest"); +const spdy = require("spdy"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/static-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const { skipTestOnWindows } = require("../helpers/conditional-test"); +const customHTTP = require("../helpers/custom-http"); +const normalizeOptions = require("../helpers/normalize-options"); +const port = require("../helpers/ports-map")["server-option"]; + +const httpsCertificateDirectory = path.resolve( + __dirname, + "../fixtures/https-certificate" +); + +const staticDirectory = path.resolve( + __dirname, + "../fixtures/static-config/public" +); + +describe("server option", () => { + describe("as string", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + describe("http", () => { + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: "http", + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).not.toEqual("h2"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("custom-http", () => { + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: path.resolve(__dirname, "../helpers/custom-http.js"), + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).not.toEqual("h2"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("https", () => { + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: "https", + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).not.toEqual("h2"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("spdy", () => { + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: "spdy", + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).toEqual("h2"); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + }); + + describe("as object", () => { + describe("ca, pfx, key and cert are buffer", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: fs.readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ), + pfx: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ), + key: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ), + cert: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are array of buffers", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: [ + fs.readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ) + ], + pfx: [ + fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ) + ], + key: [ + fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ) + ], + cert: [ + fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ) + ], + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are strings", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: fs + .readFileSync(path.join(httpsCertificateDirectory, "ca.pem")) + .toString(), + // TODO + // pfx can't be string because it is binary format + pfx: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ), + key: fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ) + .toString(), + cert: fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ) + .toString(), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are array of strings", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: [ + fs + .readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ) + .toString() + ], + // pfx can't be string because it is binary format + pfx: [ + fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ) + ], + key: [ + fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ) + .toString() + ], + cert: [ + fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ) + .toString() + ], + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are paths to files", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: path.join(httpsCertificateDirectory, "ca.pem"), + pfx: path.join(httpsCertificateDirectory, "server.pfx"), + key: path.join(httpsCertificateDirectory, "server.key"), + cert: path.join(httpsCertificateDirectory, "server.crt"), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are array of paths to files", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: [path.join(httpsCertificateDirectory, "ca.pem")], + pfx: [path.join(httpsCertificateDirectory, "server.pfx")], + key: [path.join(httpsCertificateDirectory, "server.key")], + cert: [path.join(httpsCertificateDirectory, "server.crt")], + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are symlinks", () => { + if (skipTestOnWindows("Symlinks are not supported on Windows")) { + return; + } + + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: path.join(httpsCertificateDirectory, "ca-symlink.pem"), + pfx: path.join(httpsCertificateDirectory, "server-symlink.pfx"), + key: path.join(httpsCertificateDirectory, "server-symlink.key"), + cert: path.join( + httpsCertificateDirectory, + "server-symlink.crt" + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toEqual(200); + expect(await response.text()).toContain("Heyo"); + expect(consoleMessages.map(message => message.text())).toEqual([]); + expect(pageErrors).toEqual([]); + }); + }); + + describe("ca, pfx, key and cert are buffer", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: fs.readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ), + pfx: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ), + key: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ), + cert: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are buffer, key and pfx are objects", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: fs.readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ), + pfx: [ + { + buf: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ) + } + ], + key: [ + { + pem: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ) + } + ], + cert: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("ca, pfx, key and cert are strings, key and pfx are objects", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + ca: fs + .readFileSync(path.join(httpsCertificateDirectory, "ca.pem")) + .toString(), + pfx: [ + { + // pfx can't be string because it is binary format + buf: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ) + } + ], + key: [ + { + pem: fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ) + .toString() + } + ], + cert: fs + .readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ) + .toString(), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("allow to pass more options", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + minVersion: "TLSv1.1", + ca: fs.readFileSync( + path.join(httpsCertificateDirectory, "ca.pem") + ), + pfx: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ), + key: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ), + cert: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + // puppeteer having issues accepting SSL here, throwing error net::ERR_BAD_SSL_CLIENT_AUTH_CERT, hence testing with supertest + describe('should support the "requestCert" option', () => { + let compiler; + let server; + let createServerSpy; + let req; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(https, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "https", + options: { + requestCert: true, + pfx: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.pfx") + ), + key: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.key") + ), + cert: fs.readFileSync( + path.join(httpsCertificateDirectory, "server.crt") + ), + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + req = request(server.app); + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await server.stop(); + }); + + it("should pass options to the 'https.createServer' method", async () => { + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + }); + + it("should handle GET request to index route (/)", async () => { + const response = await req.get("/"); + + expect(response.status).toMatchSnapshot("response status"); + expect(response.text).toMatchSnapshot("response text"); + }); + }); + + describe("spdy server with options", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(spdy, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: "spdy", + options: { + requestCert: false, + ca: [path.join(httpsCertificateDirectory, "ca.pem")], + pfx: [path.join(httpsCertificateDirectory, "server.pfx")], + key: [path.join(httpsCertificateDirectory, "server.key")], + cert: [path.join(httpsCertificateDirectory, "server.crt")], + passphrase: "webpack-dev-server" + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`https://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).toEqual("h2"); + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("https options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("custom server with options", () => { + let compiler; + let server; + let createServerSpy; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + createServerSpy = jest.spyOn(customHTTP, "createServer"); + + server = new Server( + { + static: { + directory: staticDirectory, + watch: false + }, + server: { + type: path.join(__dirname, "../helpers/custom-http.js"), + options: { + maxHeaderSize: 16384 + } + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + createServerSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to index route (/)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + const HTTPVersion = await page.evaluate( + () => performance.getEntries()[0].nextHopProtocol + ); + + expect(HTTPVersion).toEqual("http/1.1"); + expect( + normalizeOptions(createServerSpy.mock.calls[0][0]) + ).toMatchSnapshot("http options"); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/setup-exit-signals.test.js b/packages/rspack-dev-server/tests/e2e/setup-exit-signals.test.js new file mode 100644 index 00000000000..fa4420ea295 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/setup-exit-signals.test.js @@ -0,0 +1,118 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/simple-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["setup-exit-signals-option"]; + +describe("setupExitSignals option", () => { + describe("should handle 'SIGINT' and 'SIGTERM' signals", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + let doExit; + let exitSpy; + let stopCallbackSpy; + let stdinResumeSpy; + let closeCallbackSpy; + + const signals = ["SIGINT", "SIGTERM"]; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + setupExitSignals: true, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + doExit = false; + + exitSpy = jest.spyOn(process, "exit").mockImplementation(() => { + doExit = true; + }); + + stdinResumeSpy = jest + .spyOn(process.stdin, "resume") + .mockImplementation(() => {}); + + stopCallbackSpy = jest.spyOn(server, "stopCallback"); + + if (server.compiler.close) { + closeCallbackSpy = jest.spyOn(server.compiler, "close"); + } + }); + + afterEach(async () => { + exitSpy.mockReset(); + stdinResumeSpy.mockReset(); + signals.forEach(signal => { + process.removeAllListeners(signal); + }); + process.stdin.removeAllListeners("end"); + await browser.close(); + await server.stop(); + }); + + it.each(signals)("should close and exit on %s", async signal => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + process.emit(signal); + + await new Promise(resolve => { + const interval = setInterval(() => { + if (doExit) { + expect(stopCallbackSpy.mock.calls.length).toEqual(1); + + if (server.compiler.close) { + expect(closeCallbackSpy.mock.calls.length).toEqual(1); + } + + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + consoleMessages = consoleMessages.filter( + message => + !( + message.text().includes("Trying to reconnect...") || + message.text().includes("Disconnected") + ) + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/setup-middlewares.test.js b/packages/rspack-dev-server/tests/e2e/setup-middlewares.test.js new file mode 100644 index 00000000000..2d49c543ff2 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/setup-middlewares.test.js @@ -0,0 +1,171 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["setup-middlewares-option"]; + +describe("setupMiddlewares option", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + server = new Server( + { + setupMiddlewares: (middlewares, devServer) => { + if (!devServer) { + throw new Error("webpack-dev-server is not defined"); + } + + devServer.app.get("/setup-middleware/some/path", (_, response) => { + response.send("setup-middlewares option GET"); + }); + + devServer.app.post("/setup-middleware/some/path", (_, response) => { + response.send("setup-middlewares option POST"); + }); + + middlewares.push({ + name: "hello-world-test-two", + middleware: (req, res, next) => { + if (req.path !== "/foo/bar/baz") { + next(); + + return; + } + + res.send("Hello World without path!"); + } + }); + middlewares.push({ + name: "hello-world-test-one", + path: "/foo/bar", + middleware: (req, res) => { + res.send("Hello World with path!"); + } + }); + middlewares.push((req, res) => { + res.send("Hello World as function!"); + }); + + return middlewares; + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request to /setup-middleware/some/path route", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/setup-middleware/some/path`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + + const response1 = await page.goto(`http://127.0.0.1:${port}/foo/bar`, { + waitUntil: "networkidle0" + }); + + expect(response1.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + expect(response1.status()).toMatchSnapshot("response status"); + expect(await response1.text()).toMatchSnapshot("response text"); + + const response2 = await page.goto(`http://127.0.0.1:${port}/foo/bar/baz`, { + waitUntil: "networkidle0" + }); + + expect(response2.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + expect(response2.status()).toMatchSnapshot("response status"); + expect(await response2.text()).toMatchSnapshot("response text"); + + const response3 = await page.goto( + `http://127.0.0.1:${port}/setup-middleware/unknown`, + { + waitUntil: "networkidle0" + } + ); + + expect(response3.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + expect(response3.status()).toMatchSnapshot("response status"); + expect(await response3.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle POST request to /setup-middleware/some/path route", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "POST" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}/setup-middleware/some/path`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response headers content-type" + ); + expect(response.status()).toMatchSnapshot("response status"); + expect(await response.text()).toMatchSnapshot("response text"); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/static-directory.test.js b/packages/rspack-dev-server/tests/e2e/static-directory.test.js new file mode 100644 index 00000000000..551c7c7f63d --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/static-directory.test.js @@ -0,0 +1,686 @@ +"use strict"; + +const path = require("path"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const testServer = require("../helpers/test-server"); +const config = require("../fixtures/static-config/webpack.config"); +const port = require("../helpers/ports-map")["static-directory-option"]; +const runBrowser = require("../helpers/run-browser"); + +const staticDirectory = path.resolve(__dirname, "../fixtures/static-config"); +const publicDirectory = path.resolve(staticDirectory, "public"); +const otherPublicDirectory = path.resolve(staticDirectory, "other"); + +describe("static.directory option", () => { + describe("to directory", () => { + const nestedFile = path.resolve(publicDirectory, "assets/example.txt"); + + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + watch: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(nestedFile); + }); + + it("should handle request to index route", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to other file", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/other.html`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("Watches folder recursively", done => { + // chokidar emitted a change, + // meaning it watched the file correctly + server.staticWatchers[0].on("change", () => { + done(); + }); + + // change a file manually + setTimeout(() => { + fs.writeFileSync(nestedFile, "Heyo", "utf8"); + }, 1000); + }); + + it("Watches node_modules", done => { + const filePath = path.join(publicDirectory, "node_modules", "index.html"); + + fs.writeFileSync(filePath, "foo", "utf8"); + + // chokidar emitted a change, + // meaning it watched the file correctly + server.staticWatchers[0].on("change", () => { + fs.unlinkSync(filePath); + + done(); + }); + + // change a file manually + setTimeout(() => { + fs.writeFileSync(filePath, "bar", "utf8"); + }, 1000); + }); + }); + + describe("test listing files in folders without index.html using the option static.serveIndex: false", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + watch: true, + serveIndex: false + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should not list the files inside the assets folder (404)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/assets`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/bar`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("test listing files in folders without index.html using the option static.serveIndex: true", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + watch: true, + serveIndex: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should list the files inside the assets folder (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/assets/`, { + waitUntil: "networkidle0" + }); + + const text = await response.text(); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(text).toContain("example.txt"); + expect(text).toContain("other.txt"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/bar/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("test listing files in folders without index.html using the default static.serveIndex option (true)", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + watch: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should list the files inside the assets folder (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/assets`, { + waitUntil: "networkidle0" + }); + + const text = await response.text(); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(text).toContain("example.txt"); + expect(text).toContain("other.txt"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/bar`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("to multiple directories", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: [publicDirectory, otherPublicDirectory], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request first directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to second directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/foo.html`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("testing single & multiple external paths", () => { + let server; + + afterEach(done => { + testServer.close(() => { + done(); + }); + }); + + it("Should throw exception (external url)", done => { + expect.assertions(1); + + server = testServer.start( + config, + { + static: "https://example.com/" + }, + error => { + expect(error.message).toBe( + "Using a URL as static.directory is not supported" + ); + + server.stopCallback(done); + } + ); + }); + + it("Should not throw exception (local path with lower case first character)", done => { + testServer.start( + config, + { + static: { + directory: + publicDirectory.charAt(0).toLowerCase() + + publicDirectory.substring(1), + watch: true + }, + port + }, + done + ); + }); + + it("Should not throw exception (local path with lower case first character & has '-')", done => { + testServer.start( + config, + { + static: { + directory: "c:\\absolute\\path\\to\\content-base", + watch: true + }, + port + }, + done + ); + }); + + it("Should not throw exception (local path with upper case first character & has '-')", done => { + testServer.start( + config, + { + static: { + directory: "C:\\absolute\\path\\to\\content-base", + watch: true + }, + port + }, + done + ); + }); + + it("Should throw exception (array with absolute url)", done => { + server = testServer.start( + config, + { + static: [publicDirectory, "https://example.com/"] + }, + error => { + expect(error.message).toBe( + "Using a URL as static.directory is not supported" + ); + + server.stopCallback(done); + } + ); + }); + }); + + describe("defaults to PWD", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + jest + .spyOn(process, "cwd") + .mockImplementation(() => path.resolve(staticDirectory)); + compiler = webpack(config); + + server = new Server( + { + // eslint-disable-next-line no-undefined + static: undefined, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to /index.html", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/index.html`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("disabled", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + // This is a somewhat weird test, but it is important that we mock + // the PWD here, and test if /other.html in our "fake" PWD really is not requested. + jest.spyOn(process, "cwd").mockImplementation(() => publicDirectory); + + compiler = webpack(config); + + server = new Server( + { + static: false, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should not handle request to /other.html (404)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/index.html`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/static-public-path.test.js b/packages/rspack-dev-server/tests/e2e/static-public-path.test.js new file mode 100644 index 00000000000..38e27f50120 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/static-public-path.test.js @@ -0,0 +1,1101 @@ +"use strict"; + +const path = require("path"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/static-config/webpack.config"); +const port = require("../helpers/ports-map")["static-public-path-option"]; +const runBrowser = require("../helpers/run-browser"); + +const staticDirectory = path.resolve(__dirname, "../fixtures/static-config"); +const publicDirectory = path.resolve(staticDirectory, "public"); +const otherPublicDirectory = path.resolve(staticDirectory, "other"); +const staticPublicPath = "/serve-content-at-this-url"; +const otherStaticPublicPath = "/serve-other-content-at-this-url"; + +describe("static.publicPath option", () => { + describe("to directory", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to index", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to other file", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/other.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("test listing files in folders without index.html using the option static.serveIndex: false", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true, + serveIndex: false + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("shouldn't list the files inside the assets folder (404)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/assets`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/bar`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("test listing files in folders without index.html using the option static.serveIndex: true", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true, + serveIndex: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should list the files inside the assets folder (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/assets`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toContain("other.txt"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/bar`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("test listing files in folders without index.html using the option static.serveIndex default (true)", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true, + serveIndex: true + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should list the files inside the assets folder (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/assets`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toContain("other.txt"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should show Heyo. because bar has index.html inside it (200)", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/bar`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("to multiple directories", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: [ + { + directory: publicDirectory, + publicPath: staticPublicPath + }, + { + directory: otherPublicDirectory, + publicPath: staticPublicPath + } + ], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to first directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to second directory", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/foo.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("defaults to CWD", () => { + let cwdSpy; + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + cwdSpy = jest + .spyOn(process, "cwd") + .mockImplementation(() => staticDirectory); + + compiler = webpack(config); + + server = new Server( + { + static: { + publicPath: staticPublicPath + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + cwdSpy.mockRestore(); + + await browser.close(); + await server.stop(); + }); + + it("should handle request to page", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/index.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("Content type", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to example.txt", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/assets/example.txt`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(response.headers()["content-type"]).toMatchSnapshot( + "response header content-type" + ); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("should ignore methods other than GET and HEAD", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: { + directory: publicDirectory, + publicPath: staticPublicPath + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle GET request", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle HEAD request", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + + interceptedRequest.continue({ method: "HEAD" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should not handle POST request", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + interceptedRequest.continue({ method: "POST" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should not handle PUT request", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + interceptedRequest.continue({ method: "PUT" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should not handle DELETE request", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + interceptedRequest.continue({ method: "DELETE" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should not handle PATCH request", async () => { + await page.setRequestInterception(true); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }) + .on("request", interceptedRequest => { + interceptedRequest.continue({ method: "PATCH" }); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("multiple static.publicPath entries", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: [ + { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true + }, + { + directory: otherPublicDirectory, + publicPath: otherStaticPublicPath, + watch: true + } + ], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to the index of first path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to the other file of first path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/other.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to the /foo route of second path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${otherStaticPublicPath}/foo.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("multiple static.publicPath entries with publicPath array", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + static: [ + { + directory: publicDirectory, + publicPath: staticPublicPath, + watch: true + }, + { + directory: otherPublicDirectory, + publicPath: [staticPublicPath, otherStaticPublicPath], + watch: true + } + ], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should handle request to the index of first path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to the other file of first path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/other.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to the /foo route of first path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${staticPublicPath}/foo.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should handle request to the /foo route of second path", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto( + `http://127.0.0.1:${port}${otherStaticPublicPath}/foo.html`, + { + waitUntil: "networkidle0" + } + ); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(await response.text()).toMatchSnapshot("response text"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/stats.test.js b/packages/rspack-dev-server/tests/e2e/stats.test.js new file mode 100644 index 00000000000..2ce82602469 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/stats.test.js @@ -0,0 +1,147 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const HTMLGeneratorPlugin = require("../helpers/html-generator-plugin"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").stats; + +global.console.log = jest.fn(); + +describe("stats", () => { + const cases = [ + { + title: 'should work when "stats" is not specified', + webpackOptions: {} + }, + { + title: 'should work using "{}" value for the "stats" option', + webpackOptions: { + stats: {} + } + }, + { + title: 'should work using "undefined" value for the "stats" option', + webpackOptions: { + // eslint-disable-next-line no-undefined + stats: undefined + } + }, + { + title: 'should work using "false" value for the "stats" option', + webpackOptions: { + stats: false + } + }, + { + title: 'should work using "errors-only" value for the "stats" option', + webpackOptions: { + stats: "errors-only" + } + }, + { + title: + 'should work using "{ assets: false }" value for the "stats" option', + webpackOptions: { + stats: { + assets: false + } + } + } + // TODO: support object `config.stats.colors` + // { + // title: + // 'should work using "{ assets: false }" value for the "stats" option', + // webpackOptions: { + // stats: { + // colors: { + // green: "\u001b[32m", + // }, + // }, + // }, + // }, + // `config.stats.warningsFilter` is deprecated in favor of config.ignoreWarnings + // { + // title: + // 'should work using "{ warningsFilter: \'test\' }" value for the "stats" option', + // webpackOptions: { + // plugins: [ + // { + // apply(compiler) { + // compiler.hooks.thisCompilation.tap( + // "warnings-webpack-plugin", + // (compilation) => { + // compilation.warnings.push( + // new Error("Warning from compilation"), + // ); + // }, + // ); + // }, + // }, + // new HTMLGeneratorPlugin(), + // ], + // stats: { warningsFilter: /Warning from compilation/ }, + // }, + // }, + ]; + + if (webpack.version.startsWith("5")) { + cases.push({ + title: 'should work and respect the "ignoreWarnings" option', + webpackOptions: { + plugins: [ + { + apply(compiler) { + compiler.hooks.thisCompilation.tap( + "warnings-webpack-plugin", + compilation => { + compilation.warnings.push( + new Error("Warning from compilation") + ); + } + ); + } + }, + new HTMLGeneratorPlugin() + ], + ignoreWarnings: [/Warning from compilation/] + } + }); + } + + cases.forEach(testCase => { + it(testCase.title, async () => { + const compiler = webpack({ ...config, ...testCase.webpackOptions }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const consoleMessages = []; + + page.on("console", message => { + consoleMessages.push(message); + }); + + await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + consoleMessages.map(message => message.text()) + ).toMatchSnapshot(); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/target.test.js b/packages/rspack-dev-server/tests/e2e/target.test.js new file mode 100644 index 00000000000..0bc47381f08 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/target.test.js @@ -0,0 +1,94 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map").target; + +describe("target", () => { + const targets = [ + false, + "browserslist:defaults", + "web", + "webworker", + "node", + "async-node", + "electron-main", + "electron-preload", + "electron-renderer", + "nwjs", + "node-webkit", + "es5", + ["web", "es5"] + ]; + + for (const target of targets) { + it(`should work using "${target}" target`, async () => { + const compiler = webpack({ + ...config, + target, + ...(target === false || target === "es5" + ? { + output: { chunkFormat: "array-push", path: "/" } + } + : {}) + }); + const devServerOptions = { + port + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + // TODO: check why require is defined in theses target + // if ( + // target === "node" || + // target === "async-node" || + // target === "electron-main" || + // target === "electron-preload" || + // target === "electron-renderer" || + // target === "nwjs" || + // target === "node-webkit" + // ) { + // console.log(pageErrors); + // const hasRequireOrGlobalError = + // pageErrors.filter((pageError) => + // /require is not defined|global is not defined/.test(pageError), + // ).length === 1; + + // expect(hasRequireOrGlobalError).toBe(true); + // } else { + // expect(pageErrors).toMatchSnapshot("page errors"); + // } + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + } +}); diff --git a/packages/rspack-dev-server/tests/e2e/watch-files.test.js b/packages/rspack-dev-server/tests/e2e/watch-files.test.js new file mode 100644 index 00000000000..0e77d84d399 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/watch-files.test.js @@ -0,0 +1,682 @@ +"use strict"; + +const path = require("path"); +const chokidar = require("chokidar"); +const fs = require("graceful-fs"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/watch-files-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["watch-files-option"]; + +const watchDir = path.resolve( + __dirname, + "../fixtures/watch-files-config/public" +); + +describe("watchFiles option", () => { + describe("should work with string and path to file", () => { + const file = path.join(watchDir, "assets/example.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: file, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + + describe("should work with string and path to directory", () => { + const file = path.join(watchDir, "assets/example.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: watchDir, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + + describe("should work with string and glob", () => { + const file = path.join(watchDir, "assets/example.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: `${watchDir}/**/*`, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + + describe("should not crash if file doesn't exist", () => { + const nonExistFile = path.join(watchDir, "assets/non-exist.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + try { + fs.unlinkSync(nonExistFile); + } catch (error) { + // ignore + } + + compiler = webpack(config); + + server = new Server( + { + watchFiles: nonExistFile, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(nonExistFile); + resolve(); + }); + + // create file content + setTimeout(() => { + fs.writeFileSync(nonExistFile, "Kurosaki Ichigo", "utf8"); + // change file content + setTimeout(() => { + fs.writeFileSync(nonExistFile, "Kurosaki Ichigo", "utf8"); + }, 1000); + }, 1000); + }); + }); + }); + + describe("should work with object with single path", () => { + const file = path.join(watchDir, "assets/example.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: { paths: file }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + + describe("should work with object with multiple paths", () => { + const file = path.join(watchDir, "assets/example.txt"); + const other = path.join(watchDir, "assets/other.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: { paths: [file, other] }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + fs.truncateSync(other); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "foo", "utf8"); + fs.writeFileSync(other, "bar", "utf8"); + + await new Promise(resolve => { + const expected = [file, other]; + let changed = 0; + + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(expected.includes(changedPath)).toBeTruthy(); + + changed += 1; + + if (changed === 2) { + resolve(); + } + }); + }); + }); + }); + + describe("should work with array config", () => { + const file = path.join(watchDir, "assets/example.txt"); + const other = path.join(watchDir, "assets/other.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: [{ paths: [file] }, other], + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + fs.truncateSync(other); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "foo", "utf8"); + fs.writeFileSync(other, "bar", "utf8"); + + await new Promise(resolve => { + let changed = 0; + + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + changed += 1; + + if (changed === 2) { + resolve(); + } + }); + server.staticWatchers[1].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(other); + + changed += 1; + + if (changed === 2) { + resolve(); + } + }); + }); + }); + }); + + describe("should work with options", () => { + const file = path.join(watchDir, "assets/example.txt"); + + const chokidarMock = jest.spyOn(chokidar, "watch"); + + const optionCases = [ + { + poll: true + }, + { + poll: 200 + }, + { + usePolling: true + }, + { + usePolling: true, + poll: 200 + }, + { + usePolling: false + }, + { + usePolling: false, + poll: 200 + }, + { + usePolling: false, + poll: true + }, + { + interval: 400, + poll: 200 + }, + { + usePolling: true, + interval: 200, + poll: 400 + }, + { + usePolling: false, + interval: 200, + poll: 400 + } + ]; + + optionCases.forEach(optionCase => { + describe(JSON.stringify(optionCase), () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + chokidarMock.mockClear(); + + compiler = webpack(config); + + server = new Server( + { + watchFiles: { + paths: file, + options: optionCase + }, + port + }, + compiler + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await server.stop(); + await browser.close(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + // should pass correct options to chokidar config + expect(chokidarMock.mock.calls[0][1]).toMatchSnapshot(); + + expect(response.status()).toMatchSnapshot("response status"); + + expect( + consoleMessages.map(message => message.text()) + ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise(resolve => { + server.staticWatchers[0].on("change", async changedPath => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + }); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/web-socket-communication.test.js b/packages/rspack-dev-server/tests/e2e/web-socket-communication.test.js new file mode 100644 index 00000000000..e78eafa2317 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/web-socket-communication.test.js @@ -0,0 +1,223 @@ +"use strict"; + +const WebSocket = require("ws"); +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const WebsocketServer = require("webpack-dev-server/lib/servers/WebsocketServer"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const port = require("../helpers/ports-map")["web-socket-communication"]; + +jest.setTimeout(60000); + +describe("web socket communication", () => { + const webSocketServers = ["ws", "sockjs"]; + + webSocketServers.forEach(websocketServer => { + it(`should work and close web socket client connection when web socket server closed ("${websocketServer}")`, async () => { + WebsocketServer.heartbeatInterval = 100; + + const compiler = webpack(config); + const devServerOptions = { + port, + webSocketServer: websocketServer + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message.text()); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + await server.stop(); + await new Promise(resolve => { + const interval = setInterval(() => { + if ( + consoleMessages.includes("[webpack-dev-server] Disconnected!") + ) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + }); + + it(`should work and terminate client that is not alive ("${websocketServer}")`, async () => { + WebsocketServer.heartbeatInterval = 100; + + const compiler = webpack(config); + const devServerOptions = { + port, + webSocketServer: websocketServer + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + await browser.close(); + + // Wait heartbeat + await new Promise(resolve => { + setTimeout(() => { + resolve(); + }, 200); + }); + + expect(server.webSocketServer.clients.length).toBe(0); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + }); + + it(`should work and reconnect when the connection is lost ("${websocketServer}")`, async () => { + WebsocketServer.heartbeatInterval = 100; + + const compiler = webpack(config); + const devServerOptions = { + port, + webSocketServer: websocketServer + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + await server.stop(); + await server.start(); + + await page.waitForNavigation({ + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); + + it(`should work and do heartbeat using ("ws" web socket server)`, async () => { + WebsocketServer.heartbeatInterval = 100; + + const compiler = webpack(config); + const devServerOptions = { + port, + webSocketServer: "ws" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + server.webSocketServer.heartbeatInterval = 100; + + await new Promise((resolve, reject) => { + const ws = new WebSocket(`ws://127.0.0.1:${devServerOptions.port}/ws`, { + headers: { + host: `127.0.0.1:${devServerOptions.port}`, + origin: `http://127.0.0.1:${devServerOptions.port}` + } + }); + + let opened = false; + let received = false; + + ws.on("open", () => { + opened = true; + }); + + ws.on("error", error => { + reject(error); + }); + + ws.on("ping", () => { + if (opened && received) { + ws.close(); + } + }); + + ws.on("message", data => { + const message = JSON.parse(data); + + if (message.type === "ok") { + received = true; + } + }); + + ws.on("close", () => { + resolve(); + }); + }); + + await server.stop(); + }); +}); diff --git a/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js b/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js new file mode 100644 index 00000000000..2ab0c6db5f9 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js @@ -0,0 +1,2765 @@ +"use strict"; + +const express = require("express"); +const webpack = require("@rspack/core"); +const { createProxyMiddleware } = require("http-proxy-middleware"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const sessionSubscribe = require("../helpers/session-subscribe"); +const [port1, port2] = require("../helpers/ports-map")["web-socket-server-url"]; + +const webSocketServers = ["ws", "sockjs"]; + +describe("web socket server URL", () => { + for (const webSocketServer of webSocketServers) { + const websocketURLProtocol = webSocketServer === "ws" ? "ws" : "http"; + + it(`should work behind proxy, when hostnames are same and ports are different ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = devServerHost; + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${devServerPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + await browser.close(); + await server.stop(); + } + }); + + it(`should work behind proxy, when hostnames are different and ports are same ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = Server.internalIPSync("v4"); + const proxyPort = port1; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${devServerPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + await browser.close(); + await server.stop(); + } + }); + + it(`should work behind proxy, when hostnames are different and ports are different ("${webSocketServer}")`, async () => { + const devServerHost = "127.0.0.1"; + const devServerPort = port1; + const proxyHost = Server.internalIPSync("v4"); + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + hostname: devServerHost + } + }, + webSocketServer, + port: devServerPort, + host: devServerHost, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + function startProxy(callback) { + const app = express(); + app.use( + "/", + createProxyMiddleware({ + target: `http://${devServerHost}:${devServerPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${devServerHost}:${devServerPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + } + }); + + it(`should work behind proxy, when the "host" option is "local-ip" and the "port" option is "auto" ("${webSocketServer}")`, async () => { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = 40000; + + const proxyHost = Server.internalIPSync("v4"); + const proxyPort = port2; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: "auto", + host: "local-ip", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const resolvedHost = server.options.host; + const resolvedPort = server.options.port; + + function startProxy(callback) { + const app = express(); + + app.use( + "/", + createProxyMiddleware({ + target: `http://${resolvedHost}:${resolvedPort}`, + ws: true, + changeOrigin: true, + logLevel: "warn" + }) + ); + + return app.listen(proxyPort, proxyHost, callback); + } + + const proxy = await new Promise(resolve => { + const proxyCreated = startProxy(() => { + resolve(proxyCreated); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${proxyHost}:${proxyPort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${resolvedHost}:${resolvedPort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + proxy.close(); + + await browser.close(); + await server.stop(); + + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + } + }); + + it(`should work with the "client.webSocketURL.protocol" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + protocol: "ws:" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://localhost:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://localhost:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.protocol" option using "auto:" value ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + protocol: "auto:" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://localhost:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://localhost:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.protocol" option using "http:" value and covert to "ws:" ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + protocol: "http:" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://localhost:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://localhost:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.host" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + hostname: "127.0.0.1" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.host" option using "0.0.0.0" value ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + hostname: "0.0.0.0" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.port" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: port1 + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.port" option as string ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: `${port1}` + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with "client.webSocketURL.port" and "webSocketServer.options.port" options as string ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + webSocketServer: { + type: webSocketServer, + options: { + host: "0.0.0.0", + // "sockjs" doesn't support external server + port: webSocketServer === "sockjs" ? `${port1}` : `${port2}` + } + }, + port: port1, + host: "0.0.0.0", + client: { + webSocketURL: { + port: webSocketServer === "sockjs" ? `${port1}` : `${port2}` + } + }, + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + webSocketServer === "sockjs" + ? `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + : `${websocketURLProtocol}://127.0.0.1:${port2}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.port" option using "0" value ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + port: 0 + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.pathname" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: "/ws" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with default "/ws" value of the "client.webSocketURL.pathname" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.username" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + username: "zenitsu" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://zenitsu@127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.password" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: + webSocketServer === "ws" + ? { + username: "foo", + password: "chuntaro" + } + : {} + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + // "sockjs" has bug with parsing URL + webSocketServer === "ws" + ? `${websocketURLProtocol}://foo:chuntaro@127.0.0.1:${port1}/ws` + : `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.username" and "client.webSocketURL.password" option ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + username: "zenitsu", + password: "chuntaro" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://zenitsu:chuntaro@127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the custom web socket server "path" ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + webSocketServer: { + type: webSocketServer, + options: { + path: "/custom-ws/foo/bar" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\/foo\/bar/.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws/foo/bar` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + // Only works for "ws" server + it(`should work with the custom web socket server "path" using empty value ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + webSocketServer: { + type: webSocketServer, + options: { + path: webSocketServer === "ws" ? "" : "/custom-ws" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + webSocketServer === "ws" + ? `${websocketURLProtocol}://127.0.0.1:${port1}` + : `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: "/custom-ws/foo/bar" + } + }, + webSocketServer: { + type: webSocketServer, + options: { + path: "/custom-ws/foo/bar" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\/foo\/bar/.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws/foo/bar` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending without slash ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: "/custom-ws" + } + }, + webSocketServer: { + type: webSocketServer, + options: { + path: "/custom-ws" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + // Only works for "ws" server, "sockjs" adds "/" be default, because need do requests like "/custom-ws/info?t=1624462615772" + it(`should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" ending with slash ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: webSocketServer === "ws" ? "/custom-ws/" : "/custom-ws" + } + }, + webSocketServer: { + type: webSocketServer, + options: { + path: webSocketServer === "ws" ? "/custom-ws/" : "/custom-ws" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws/` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + // Only works for "ws" server + it(`should work with the "client.webSocketURL.pathname" option and the custom web socket server "path" using empty value ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: webSocketServer === "ws" ? "" : "/custom-ws" + } + }, + webSocketServer: { + type: webSocketServer, + options: { + path: webSocketServer === "ws" ? "" : "/custom-ws" + } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + webSocketServer === "ws" + ? `${websocketURLProtocol}://127.0.0.1:${port1}` + : `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + // Only works for "sockjs" server + it(`should work with the "client.webSocketURL.pathname" option and the custom web socket server "prefix" for compatibility with "sockjs" ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + pathname: "/custom-ws" + } + }, + webSocketServer: { + type: webSocketServer, + options: + webSocketServer === "ws" + ? { path: "/custom-ws" } + : { prefix: "/custom-ws" } + }, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/custom-ws/.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/custom-ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work when "host" option is IPv4 ("${webSocketServer}")`, async () => { + const hostname = Server.internalIPSync("v4"); + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + host: hostname + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://${hostname}:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${hostname}:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work when "host" option is "local-ip" ("${webSocketServer}")`, async () => { + const hostname = Server.internalIPSync("v4"); + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + host: "local-ip" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://${hostname}:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${hostname}:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work when "host" option is "local-ipv4" ("${webSocketServer}")`, async () => { + const hostname = Server.internalIPSync("v4"); + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + host: "local-ipv4" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + await page.goto(`http://${hostname}:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://${hostname}:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with "server: 'https'" option ("${webSocketServer}")`, async () => { + const hostname = "127.0.0.1"; + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + server: "https" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`https://${hostname}:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + if (webSocketServer === "ws") { + expect(webSocketRequest.url).toContain( + `wss://${hostname}:${port1}/ws` + ); + } else { + expect(webSocketRequest.url).toContain( + `https://${hostname}:${port1}/ws` + ); + } + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with "server: 'spdy'" option ("${webSocketServer}")`, async () => { + const hostname = "127.0.0.1"; + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: port1, + server: "spdy" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`https://${hostname}:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + if (webSocketServer === "ws") { + expect(webSocketRequest.url).toContain( + `wss://${hostname}:${port1}/ws` + ); + } else { + expect(webSocketRequest.url).toContain( + `https://${hostname}:${port1}/ws` + ); + } + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work when "port" option is "auto" ("${webSocketServer}")`, async () => { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = 50000; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer, + port: "auto", + host: "0.0.0.0" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const resolvedFreePort = server.options.port; + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${resolvedFreePort}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${resolvedFreePort}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + } + }); + + it(`should work with "client.webSocketURL.*" options ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: { + protocol: "ws:", + hostname: "127.0.0.1", + port: port1, + pathname: "/ws" + } + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with the "client.webSocketURL" option as "string" ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: `ws://127.0.0.1:${port1}/ws` + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + + if (webSocketServer === "ws") { + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + } else { + page.on("request", request => { + if (/\/ws\//.test(request.url())) { + webSocketRequests.push({ url: request.url() }); + } + }); + } + + await page.goto(`http://127.0.0.1:${port1}/`, { + waitUntil: "networkidle0" + }); + + const webSocketRequest = webSocketRequests[0]; + + expect(webSocketRequest.url).toContain( + `${websocketURLProtocol}://127.0.0.1:${port1}/ws` + ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work and throw an error on invalid web socket URL ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: "unknown://unknown.unknown/unknown" + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://localhost:${port1}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect( + pageErrors.map(pageError => pageError.message.split("\n")[0]) + ).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should not work and output disconnect wrong web socket URL ("${webSocketServer}")`, async () => { + const compiler = webpack(config); + const devServerOptions = { + client: { + webSocketURL: "ws://unknown.unknown/unknown" + }, + webSocketServer, + port: port1, + host: "0.0.0.0", + allowedHosts: "all" + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + let isDisconnected = false; + + page + .on("console", message => { + const text = message.text(); + + if (!isDisconnected) { + isDisconnected = /Disconnected!/.test(text); + consoleMessages.push(text.replace(/:[\d]+/g, ":")); + } + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://localhost:${port1}/`, { + waitUntil: "networkidle0" + }); + + await new Promise(resolve => { + const interval = setInterval(() => { + if ( + consoleMessages.includes("[webpack-dev-server] Disconnected!") + ) { + clearInterval(interval); + + resolve(); + } + }, 100); + }); + + expect(consoleMessages).toMatchSnapshot("console messages"); + expect( + pageErrors.map(pageError => pageError.message.split("\n")[0]) + ).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + } +}); diff --git a/packages/rspack-dev-server/tests/e2e/web-socket-server.test.js b/packages/rspack-dev-server/tests/e2e/web-socket-server.test.js new file mode 100644 index 00000000000..44ba6ac53f8 --- /dev/null +++ b/packages/rspack-dev-server/tests/e2e/web-socket-server.test.js @@ -0,0 +1,68 @@ +"use strict"; + +const webpack = require("@rspack/core"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); +const config = require("../fixtures/client-config/webpack.config"); +const runBrowser = require("../helpers/run-browser"); +const sessionSubscribe = require("../helpers/session-subscribe"); +const port = require("../helpers/ports-map")["web-socket-server-test"]; + +describe("web socket server", () => { + it("should work allow to disable", async () => { + const devServerPort = port; + + const compiler = webpack(config); + const devServerOptions = { + webSocketServer: false, + port: devServerPort + }; + const server = new Server(devServerOptions, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(webSocketRequests).toHaveLength(0); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); +}); diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/ca-symlink.pem b/packages/rspack-dev-server/tests/fixtures/https-certificate/ca-symlink.pem new file mode 120000 index 00000000000..e375f5ab0d5 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/ca-symlink.pem @@ -0,0 +1 @@ +ca.pem \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/ca.pem b/packages/rspack-dev-server/tests/fixtures/https-certificate/ca.pem new file mode 100644 index 00000000000..05b314535be --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/ca.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kv +C/hf5Ei1J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYu +Dy9WkFuMie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhs +EENnH6sUE9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2sw +duxJTWRINmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+ +T8emgklStASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABAoIBAGqWKPE1QnT3T+3J +G+ITz9P0dDFbvWltlTZmeSJh/s2q+WZloUNtBxdmwbqT/1QecnkyGgyzVCjvSKsu +CgVjWNVAhysgtNtxRT4BVflffBXLVH2qsBjpsLRGU6EcMXuPGTiEp3YRHNuO6Aj8 +oP8fEsCGPc9DlJMGgxQRAKlrVF8TN/0j6Qk+YpS4MZ0YFQfBY+WdKu04Z8TVTplQ +tTkiGpBI+Oj85jF59aQiizglJgADkAZ6zmbrctm/G9jPxh7JLS2cKI0ECZgK5yAc +pk10E1YWhoCksjr9arxy6fS9TiX9P15vv06k+s7c4c5X7XDm3X0GWeSbqBMJb8q7 +BhZQNzECgYEA4kAtymDBvFYiZFq7+lzQBRKAI1RCq7YqxlieumH0PSkie2bba3dW +NVdTi7at8+GDB9/cHUPKzg/skfJllek57MZmusiVwB/Lmp/IlW8YyGShdYZ7zQsV +KMWJljpky3BEDM5sb08wIkfrOkelI/S4Bqqabd9JzOMJzoTiVOlMam8CgYEA3ctN +yonWz2bsnCUstQvQCLdI5a8Q7GJvlH2awephxGXIKGUuRmyyop0AnRnIBEWtOQV7 +yZjW32bU+Wt+2BJ247EyJiIQ4gT+T51t+v/Wt1YNbL3dSj9ttOvwYd4H2W4E7EIO +GKIF4I39FM7r8NfG7YE7S1aVcnrqs01N3nhd9HMCgYEAjepbzpmqbAxLPk97oase +AFB+d6qetz5ozklAJwDSRprKukTmVR5hwMup5/UKX/OQURwl4WVojKCIb3NwLPxC +DTbVsUuoQv6uo6qeEr3A+dHFRQa6GP9eolhl2Ql/t+wPg0jn01oEgzxBXCkceNVD +qUrR2yE4FYBD4nqPzVsZR5kCgYEA1yTi7NkQeldIpZ6Z43T18753A/Xx4JsLyWqd +uAT3mV9x7V1Yqg++qGbLtZjQoPRFt85N6ZxMsqA5b0iK3mXq1auJDdx1rAlT9z6q +9JM/YNAkbZsvEVq9vIYxw31w98T1GYhpzBM+yDhzir+9tv5YhQKa1dXDWi1JhWwz +YN45pWkCgYEAxuVsJ4D4Th5o050ppWpnxM/WuMhIUKqaoFTVucMKFzn+g24y9pv5 +miYdNYIk4Y+4pzHG6ZGZSHJcQ9BLui6H/nLQnqkgCb2lT5nfp7/GKdus7BdcjPGs +fcV46yL7/X0m8nDb3hkwwrDTU4mKFkMrzKpjdZBsttEmW0Aw/3y36gU= +-----END RSA PRIVATE KEY----- diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.crt b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.crt new file mode 120000 index 00000000000..d23ad4d88e3 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.crt @@ -0,0 +1 @@ +server.crt \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.key b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.key new file mode 120000 index 00000000000..c26f438dd74 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.key @@ -0,0 +1 @@ +server.key \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.pfx b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.pfx new file mode 120000 index 00000000000..89adfe8705f --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/server-symlink.pfx @@ -0,0 +1 @@ +server.pfx \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server.crt b/packages/rspack-dev-server/tests/fixtures/https-certificate/server.crt new file mode 100644 index 00000000000..1992bb16104 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/server.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJALz8gD/gAt0OMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTgxMDIzMTgyMTQ5WhcNMTkxMDIzMTgyMTQ5WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAxAUVLFM+K3XDLQkBi7xt0s1Ip7JoHYDskzUDQNHjjMkUq5kvC/hf5Ei1 +J6qruJs3Xqg86Nl4+ed4ynUajAkRRibhp0P1SG1tgPssIK6iC7g8heYuDy9WkFuM +ie0513zjSn6bMEAK5TegxYAWCbaCZX/Fw9bDniabL/zuOv4sf8J4EPhsEENnH6sU +E9HxPUgQmNt1Tbd0j1Cd5PXrSTLyfVPRh0m9QhXTUHuxsse8XSn9U2swduxJTWRI +NmhffYn+O7kbJGI77xYr8u58Rsf3HCMI8DTKZNvQLChvvtLJ9ckyu7Q+T8emgklS +tASm3V2UtnriaK/IQEhgSdxqVRib3QIDAQABo1AwTjAdBgNVHQ4EFgQUDZBhVKdb +3BRhLIhuuE522Vsul0IwHwYDVR0jBBgwFoAUDZBhVKdb3BRhLIhuuE522Vsul0Iw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABh9WWZwWLgb9/DcTxL72 +6pI96t4jiF79Q+pPefkaIIi0mE6yodWrTAsBQu9I6bNRaEcCSoiXkP2bqskD/UGg +LwUFgSrDOAA3UjdHw3QU5g2NocduG7mcFwA40TB98sOsxsUyYlzSyWzoiQWwPYwb +hek1djuWkqPXsTjlj54PTPN/SjTFmo4p5Ip6nbRf2nOREl7v0rJpGbJvXiCMYyd+ +Zv+j4mRjCGo8ysMR2HjCUGkYReLAgKyyz3M7i8vevJhKslyOmy6Txn4F0nPVumaU +DDIy4xXPW1STWfsmSYJfYW3wa0wk+pJQ3j2cTzkPQQ8gwpvM3U9DJl43uwb37v6I +7Q== +-----END CERTIFICATE----- diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server.key b/packages/rspack-dev-server/tests/fixtures/https-certificate/server.key new file mode 100644 index 00000000000..c002d19e496 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/https-certificate/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEBRUsUz4rdcMt +CQGLvG3SzUinsmgdgOyTNQNA0eOMyRSrmS8L+F/kSLUnqqu4mzdeqDzo2Xj553jK +dRqMCRFGJuGnQ/VIbW2A+ywgrqILuDyF5i4PL1aQW4yJ7TnXfONKfpswQArlN6DF +gBYJtoJlf8XD1sOeJpsv/O46/ix/wngQ+GwQQ2cfqxQT0fE9SBCY23VNt3SPUJ3k +9etJMvJ9U9GHSb1CFdNQe7Gyx7xdKf1TazB27ElNZEg2aF99if47uRskYjvvFivy +7nxGx/ccIwjwNMpk29AsKG++0sn1yTK7tD5Px6aCSVK0BKbdXZS2euJor8hASGBJ +3GpVGJvdAgMBAAECggEAapYo8TVCdPdP7ckb4hPP0/R0MVu9aW2VNmZ5ImH+zar5 +ZmWhQ20HF2bBupP/VB5yeTIaDLNUKO9Iqy4KBWNY1UCHKyC023FFPgFV+V98FctU +faqwGOmwtEZToRwxe48ZOISndhEc247oCPyg/x8SwIY9z0OUkwaDFBEAqWtUXxM3 +/SPpCT5ilLgxnRgVB8Fj5Z0q7ThnxNVOmVC1OSIakEj46PzmMXn1pCKLOCUmAAOQ +BnrOZuty2b8b2M/GHsktLZwojQQJmArnIBymTXQTVhaGgKSyOv1qvHLp9L1OJf0/ +Xm+/TqT6ztzhzlftcObdfQZZ5JuoEwlvyrsGFlA3MQKBgQDiQC3KYMG8ViJkWrv6 +XNAFEoAjVEKrtirGWJ66YfQ9KSJ7Zttrd1Y1V1OLtq3z4YMH39wdQ8rOD+yR8mWV +6Tnsxma6yJXAH8uan8iVbxjIZKF1hnvNCxUoxYmWOmTLcEQMzmxvTzAiR+s6R6Uj +9LgGqppt30nM4wnOhOJU6UxqbwKBgQDdy03KidbPZuycJSy1C9AIt0jlrxDsYm+U +fZrB6mHEZcgoZS5GbLKinQCdGcgERa05BXvJmNbfZtT5a37YEnbjsTImIhDiBP5P +nW36/9a3Vg1svd1KP2206/Bh3gfZbgTsQg4YogXgjf0Uzuvw18btgTtLVpVyeuqz +TU3eeF30cwKBgQCN6lvOmapsDEs+T3uhqx4AUH53qp63PmjOSUAnANJGmsq6ROZV +HmHAy6nn9Qpf85BRHCXhZWiMoIhvc3As/EINNtWxS6hC/q6jqp4SvcD50cVFBroY +/16iWGXZCX+37A+DSOfTWgSDPEFcKRx41UOpStHbITgVgEPieo/NWxlHmQKBgQDX +JOLs2RB6V0ilnpnjdPXzvncD9fHgmwvJap24BPeZX3HtXViqD76oZsu1mNCg9EW3 +zk3pnEyyoDlvSIreZerVq4kN3HWsCVP3Pqr0kz9g0CRtmy8RWr28hjHDfXD3xPUZ +iGnMEz7IOHOKv722/liFAprV1cNaLUmFbDNg3jmlaQKBgQDG5WwngPhOHmjTnSml +amfEz9a4yEhQqpqgVNW5wwoXOf6DbjL2m/maJh01giThj7inMcbpkZlIclxD0Eu6 +Lof+ctCeqSAJvaVPmd+nv8Yp26zsF1yM8ax9xXjrIvv9fSbycNveGTDCsNNTiYoW +QyvMqmN1kGy20SZbQDD/fLfqBQ== +-----END PRIVATE KEY----- diff --git a/packages/rspack-dev-server/tests/fixtures/https-certificate/server.pfx b/packages/rspack-dev-server/tests/fixtures/https-certificate/server.pfx new file mode 100644 index 0000000000000000000000000000000000000000..e5ed9c7988603578422a0aeca51ae535b42949ab GIT binary patch literal 2469 zcmZXUc{CJ?9>>jMm>FcxR`y{CGxp^&8bgt+kwWC!GbD{=EHNWHVeFC-*|L_DFh!Ob zZYW#aEQ5pyS-Zk>&w20Nch39ccYf!5zvq1a`+QI&XbBJiLXn_e5U4`j#kf6A02_cx zf;xan&?_gg6^aCA`=^3XNuYfc3A7Cau$;WLe;NQv3{ZB7nr?ldKnlndSnCy zva>{!KyTGwWU=?tJ6`FqHvXvu^e?Tim!78v)a*dXKDmo5^Z3mjmAEf?&y!ANZ&E;_+sRkuINkOz-Od@Q`C~Ccs>)Qq&@8 zj=|@(g=k7~o*lT(3j7DXHXTQbEnvLNZmzy*flqCoPppt%SF}e8)Am%i`_?U7HCK64 z2Mn+DYFYU~*R_qiy-SAK$hWDS&I{*oUYomZF_Q_RpyIoEq(B$jtJY(a9UkOpLE@2h zIwe>U_`%C;)&G1HI7NTF-Gp00yv%+j5xrf4P{Hs#<^ZPlulCqXV?S~1 zXSlDJCH+dhqh8fsJ1Cyn|Ij9Mi8;jbBKD=hyke+oN6qy7<1?#nTK=I2m(Hq>##2gs zLGHmkpXkQ%w&`(HN*-aN zrxC&Ak+o*R_p#vksojQy#XCEV*5xhlGn_!}uYD4YF-@(o*oQ?ph&YM$6kY0vwcJc? zkGw*?C@iahf$e)~Pch}f|3w|yk-RQ^t>w}Sn_?nWMs-F{BI~wsQVe(b^9?joaZ1EfeL^ei38-)Um{;0-~ zHx#9}H~mfB{b^z@CF8oitqZ*vi(3 z|5k()buF`X{3!mDm34pEbh<|1meo1E26M_)| zaLi0mGy>vdN&HqRB8v^75apGRyS-J+z_bJ-gfxfGK^Gp&H%FL+BAe-913%60{#El- z@|t+vvAWwrH+`#|`TY%NLR}gcU+wsAaM2C_Mg_s^(cV;oyO8Va?h<5Obvlrj%$f&R z4jpp-0u3F;TG(l?3(mhWX*2g)vw1gH{-(8@|L7(;*=|>}XeH*fLagmZXQVF8qO#qP z+vkO=_{?|T1j`UY)d_z z!vH`k3H<#;9V`ED1wIlXWUg3t^KUDFNnm;us%yb zg&x&*;>SmEHQQ+23B-&Fb=1n0XUv8D9493+-SYQQBMIQhkB?&8MYdJ=>w`pSB`!Yp zV2*<6^3!gR6j(Jf5Oovvbv9v}Q3>$59q2peI{C@$<(4r)u$MbMvYq33Fl+|}rMZ05 zY@6aB=~q&I^Ie`v9t(@iX-W32Qc%lyPDHjg+DyD>pIbjI6@1!Kruu`=xMWv!-;(qa zZKCM(<3*=v&UFb@kA+wVxe-mZxzqpw>oFIrrY%<&0*`{17BVf4G1z~MyS&siZ$uw5 zEv&zK9LFTIzzezd#d5Z9CeL0zPTWv8l4Z=A->}Tchd}9o?EssJA5xt-Ue^shVU2<@ zjON&~d>(dfgxPrja*=UH=pb)$2w|~1JFnxSf)oYI)B)skU{9T)Izh z2r>`<>@0(VYvM}9TtB{Bt&(@|jv)^~p%s9)Grr3~1z4jsdATUOSc=yOTCMpw(7>V> z*7T;p!@K-TwJnCSJ46^$ioz>x`jymuTcv^P)@#7H+$tkpNKnzBwZ}dFYy`s)(u3@M zNQ!rJJgni1kzjO_e?5y=n397x`$b=k-55=^hdSt8hEXgf7|+6U31@8IUb%KDfj;c- z;r|5racOV4AV!B~h?U5T3vf5ehet5m=Vgg5K`Kf)y-jMu^`WhZUYXkH%Hha{y*hP& z-3|NT0%_+hsoG(QCld=(ptV+uNaZ*&nh<4kKzrjL_;noWtDW=1UY50Iy zooHrAzR>`GhS2?<9`gX5^VKQt-iM?ECmRanfcIXs3KbKSZQTde!&cco|{nq_(XrPJ4YWB-g4Zj2XDfO@u4&0Iu-7IJG zrfALFXgKshz~K{daa^DCYm0n7euqub0)7F>PfzWvTdghYK7|`LUh42n*f6`?p478D z^ZurG+YLSEmEwE%?Zc>&mUeGV_A`aQE5pPVEn=!k#X-!gAph zUl(&$Vd>G+B6AP5-CBiO`+kpI%I;>qQ-&roF^`uc=lPN2S{mFPdHH&l#IBN!QW;wx z+HsC;umWCo`0%)4tDVT*FO&Vjj(VP1=`d8n5Gl=8>}*UD1vXywcRG0O*=X7rL?f%n zUXYF;rGd>H{~#JvXw?KGw02rKkI3|R& zcd>?M+*qV{N=xQmDC$CuV%hR|aTMVOnpSMqves+=n*EAhwG)q$Rljg?8WUG|_tX|= z0R!q*X2lWDkNWh3S*T}*_}R*mJQ%p&8mIe8KV0naS61%m!6xiTkh|lv>e

v@A;c zzjlNQpasGFWrNeD)tSwCe6X(p#@XY1zOyU@6dEOofRu(Wnd*zsM)@z2m d>y1l8XL5QEAm5`7!R+?&R}~i_)?t5J@?T~ed8PmW literal 0 HcmV?d00001 diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js deleted file mode 100644 index 11e40ee4d5d..00000000000 --- a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/webpack.config.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; - -const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); - -module.exports = [ - { - target: "web", - mode: "development", - context: __dirname, - stats: "none", - entry: "./foo.js", - output: { - path: "/" - }, - node: false, - infrastructureLogging: { - level: "info", - stream: { - write: () => {} - } - }, - plugins: [new HTMLGeneratorPlugin()] - } -]; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/foo.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/foo.js new file mode 100644 index 00000000000..538ae3e1b1f --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/foo.js @@ -0,0 +1,9 @@ +"use strict"; + +// 'npm run prepare' must be run for this to work during testing +const WebsocketClient = + require("webpack-dev-server/client/clients/WebSocketClient").default; + +window.expectedClient = WebsocketClient; +// eslint-disable-next-line camelcase, no-undef +window.injectedClient = __webpack_dev_server_client__.default; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/webpack.config.js new file mode 100644 index 00000000000..661a14db435 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-default/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/foo.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/foo.js new file mode 100644 index 00000000000..3b43d16ad13 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/foo.js @@ -0,0 +1,9 @@ +"use strict"; + +// 'npm run prepare' must be run for this to work during testing +const SockJSClient = + require("webpack-dev-server/client/clients/SockJSClient").default; + +window.expectedClient = SockJSClient; +// eslint-disable-next-line camelcase, no-undef +window.injectedClient = __webpack_dev_server_client__.default; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/webpack.config.js new file mode 100644 index 00000000000..661a14db435 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-sockjs-config/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/foo.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/foo.js new file mode 100644 index 00000000000..538ae3e1b1f --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/foo.js @@ -0,0 +1,9 @@ +"use strict"; + +// 'npm run prepare' must be run for this to work during testing +const WebsocketClient = + require("webpack-dev-server/client/clients/WebSocketClient").default; + +window.expectedClient = WebsocketClient; +// eslint-disable-next-line camelcase, no-undef +window.injectedClient = __webpack_dev_server_client__.default; diff --git a/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/webpack.config.js new file mode 100644 index 00000000000..661a14db435 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/provide-plugin-ws-config/webpack.config.js @@ -0,0 +1,21 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config-2/foo.js b/packages/rspack-dev-server/tests/fixtures/reload-config-2/foo.js new file mode 100644 index 00000000000..37887d25359 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config-2/foo.js @@ -0,0 +1,4 @@ +"use strict"; + +// eslint-disable-next-line import/no-unresolved +require("./main.css"); diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config-2/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/reload-config-2/webpack.config.js new file mode 100644 index 00000000000..adb992fb269 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config-2/webpack.config.js @@ -0,0 +1,29 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + path: "/" + }, + module: { + rules: [ + { + test: /\.css$/, + use: [{ loader: "style-loader" }, { loader: "css-loader" }] + } + ] + }, + node: false, + infrastructureLogging: { + level: "info", + stream: { + write: () => {} + } + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css new file mode 100644 index 00000000000..585e8f34510 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css @@ -0,0 +1 @@ +body { background-color: rgb(0, 0, 255); } \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js b/packages/rspack-dev-server/tests/fixtures/static-config/foo.js similarity index 100% rename from packages/rspack-dev-server/tests/fixtures/multi-compiler-one-configuration copy/foo.js rename to packages/rspack-dev-server/tests/fixtures/static-config/foo.js diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/other/foo.html b/packages/rspack-dev-server/tests/fixtures/static-config/other/foo.html new file mode 100644 index 00000000000..c57ed97e227 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/other/foo.html @@ -0,0 +1 @@ +Foo! diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/assets/example.txt b/packages/rspack-dev-server/tests/fixtures/static-config/public/assets/example.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/assets/other.txt b/packages/rspack-dev-server/tests/fixtures/static-config/public/assets/other.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/bar/index.html b/packages/rspack-dev-server/tests/fixtures/static-config/public/bar/index.html new file mode 100644 index 00000000000..17654db5a15 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/public/bar/index.html @@ -0,0 +1 @@ +Heyo diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/foo.wasm b/packages/rspack-dev-server/tests/fixtures/static-config/public/foo.wasm new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/index.html b/packages/rspack-dev-server/tests/fixtures/static-config/public/index.html new file mode 100644 index 00000000000..20f073ee9d7 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/public/index.html @@ -0,0 +1 @@ +Heyo. diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/.gitkeep b/packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/index.html b/packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/index.html new file mode 100644 index 00000000000..ba0e162e1c4 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/public/node_modules/index.html @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/public/other.html b/packages/rspack-dev-server/tests/fixtures/static-config/public/other.html new file mode 100644 index 00000000000..6b31da1a64c --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/public/other.html @@ -0,0 +1 @@ +Other html diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/static/index.html b/packages/rspack-dev-server/tests/fixtures/static-config/static/index.html new file mode 100644 index 00000000000..20f073ee9d7 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/static/index.html @@ -0,0 +1 @@ +Heyo. diff --git a/packages/rspack-dev-server/tests/fixtures/static-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/static-config/webpack.config.js new file mode 100644 index 00000000000..71928769b24 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/static-config/webpack.config.js @@ -0,0 +1,14 @@ +"use strict"; + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + publicPath: "/" + }, + infrastructureLogging: { + level: "warn" + } +}; diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/foo.js b/packages/rspack-dev-server/tests/fixtures/watch-files-config/foo.js new file mode 100644 index 00000000000..3c915dbcb8e --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/foo.js @@ -0,0 +1,3 @@ +"use strict"; + +console.log("Hey."); diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/other/foo.html b/packages/rspack-dev-server/tests/fixtures/watch-files-config/other/foo.html new file mode 100644 index 00000000000..c57ed97e227 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/other/foo.html @@ -0,0 +1 @@ +Foo! diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/example.txt b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/example.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/non-exist.txt b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/non-exist.txt new file mode 100644 index 00000000000..9c6e47bc1f0 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/non-exist.txt @@ -0,0 +1 @@ +Kurosaki Ichigo \ No newline at end of file diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/other.txt b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/assets/other.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/bar/index.html b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/bar/index.html new file mode 100644 index 00000000000..17654db5a15 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/bar/index.html @@ -0,0 +1 @@ +Heyo diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/other.html b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/other.html new file mode 100644 index 00000000000..6b31da1a64c --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/public/other.html @@ -0,0 +1 @@ +Other html diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/static/index.html b/packages/rspack-dev-server/tests/fixtures/watch-files-config/static/index.html new file mode 100644 index 00000000000..20f073ee9d7 --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/static/index.html @@ -0,0 +1 @@ +Heyo. diff --git a/packages/rspack-dev-server/tests/fixtures/watch-files-config/webpack.config.js b/packages/rspack-dev-server/tests/fixtures/watch-files-config/webpack.config.js new file mode 100644 index 00000000000..7df3e7e4dbb --- /dev/null +++ b/packages/rspack-dev-server/tests/fixtures/watch-files-config/webpack.config.js @@ -0,0 +1,17 @@ +"use strict"; + +const HTMLGeneratorPlugin = require("../../helpers/html-generator-plugin"); + +module.exports = { + mode: "development", + context: __dirname, + stats: "none", + entry: "./foo.js", + output: { + publicPath: "/" + }, + infrastructureLogging: { + level: "warn" + }, + plugins: [new HTMLGeneratorPlugin()] +}; diff --git a/packages/rspack-dev-server/tests/helpers/conditional-test.js b/packages/rspack-dev-server/tests/helpers/conditional-test.js new file mode 100644 index 00000000000..5d0ba713c62 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/conditional-test.js @@ -0,0 +1,12 @@ +"use strict"; + +const isWindows = process.platform === "win32"; + +function skipTestOnWindows(reason) { + if (isWindows) { + test.skip(reason, () => {}); + } + return isWindows; +} + +module.exports.skipTestOnWindows = skipTestOnWindows; diff --git a/packages/rspack-dev-server/tests/helpers/custom-http.js b/packages/rspack-dev-server/tests/helpers/custom-http.js new file mode 100644 index 00000000000..0678c55d9a3 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/custom-http.js @@ -0,0 +1,5 @@ +"use strict"; + +const customHTTP = require("http"); + +module.exports = customHTTP; diff --git a/packages/rspack-dev-server/tests/helpers/normalize-options.js b/packages/rspack-dev-server/tests/helpers/normalize-options.js new file mode 100644 index 00000000000..46e58720084 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/normalize-options.js @@ -0,0 +1,38 @@ +"use strict"; + +function normalizeOptions(options) { + const normalizedOptions = {}; + + // eslint-disable-next-line guard-for-in + for (const propertyName in options) { + let value = options[propertyName]; + + if (Array.isArray(value)) { + value = value.map(item => { + if (Buffer.isBuffer(item)) { + return ""; + } else if ( + typeof item.pem !== "undefined" && + Buffer.isBuffer(item.pem) + ) { + item.pem = ""; + } else if ( + typeof item.buf !== "undefined" && + Buffer.isBuffer(item.buf) + ) { + item.buf = ""; + } + + return item; + }); + } else if (Buffer.isBuffer(value)) { + value = ""; + } + + normalizedOptions[propertyName] = value; + } + + return normalizedOptions; +} + +module.exports = normalizeOptions; diff --git a/packages/rspack-dev-server/tests/helpers/test-server.js b/packages/rspack-dev-server/tests/helpers/test-server.js new file mode 100644 index 00000000000..027acd741b4 --- /dev/null +++ b/packages/rspack-dev-server/tests/helpers/test-server.js @@ -0,0 +1,95 @@ +"use strict"; + +const webpack = require("webpack"); +const { RspackDevServer: Server } = require("@rspack/dev-server"); + +let server; + +// start server, returning the full setup of the server +// (both the server and the compiler) +function startFullSetup(config, options, done) { + // disable watching by default for tests + if (typeof options.static === "undefined") { + options.static = false; + } else if (options.static === null) { + // this provides a way of using the default static value + delete options.static; + } + + const compiler = webpack(config); + + server = new Server(options, compiler); + + server.startCallback(error => { + if (error && done) { + return done(error); + } + + if (done) { + done(); + } + }); + + return { + server, + compiler + }; +} + +function startAwaitingCompilationFullSetup(config, options, done) { + let readyCount = 0; + + const ready = error => { + if (error && done) { + done(error); + + return; + } + + readyCount += 1; + + if (readyCount === 2) { + done(); + } + }; + + const fullSetup = startFullSetup(config, options, ready); + + // wait for compilation, since dev server can start before this + // https://github.com/webpack/webpack-dev-server/issues/847 + fullSetup.compiler.hooks.done.tap("done", () => { + ready(); + }); + + return fullSetup; +} + +function startAwaitingCompilation(config, options, done) { + return startAwaitingCompilationFullSetup(config, options, done).server; +} + +function start(config, options, done) { + // I suspect that almost all tests need to wait for compilation to + // finish, because not doing so leaves open handles for jest, + // in the case where a compilation didn't finish before destroying + // the server and moving on. Thus, the default "start" should wait + // for compilation, and only special cases where you don't expect + // a compilation happen should use startBeforeCompilation + return startAwaitingCompilation(config, options, done); +} + +function close(done) { + if (server) { + server.stopCallback(() => { + server = null; + done(); + }); + } else { + done(); + } +} + +module.exports = { + start, + close +}; From 9031ab1af51ee9e79cfb0fa33336ad17a8412432 Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 12 Sep 2024 16:57:58 +0800 Subject: [PATCH 33/51] docs: add `sources` JavaScript API (#7876) --- .../docs/en/api/javascript-api/compiler.mdx | 55 +++++++++++++------ website/docs/en/api/javascript-api/index.mdx | 15 ++++- .../docs/zh/api/javascript-api/compiler.mdx | 55 +++++++++++++------ website/docs/zh/api/javascript-api/index.mdx | 15 ++++- 4 files changed, 102 insertions(+), 38 deletions(-) diff --git a/website/docs/en/api/javascript-api/compiler.mdx b/website/docs/en/api/javascript-api/compiler.mdx index cd9b932262c..106e44a2390 100644 --- a/website/docs/en/api/javascript-api/compiler.mdx +++ b/website/docs/en/api/javascript-api/compiler.mdx @@ -269,15 +269,34 @@ isChild(): boolean; See [compiler hooks](/api/plugin-api/compiler-hooks) for more details. -### rspack/webpack +### rspack -**Type:** `typeof rspack` +- **Type:** `typeof rspack` -Get the exports of @rspack/core to obtain the associated internal objects. This is usually very useful when Rspack is wrapped or there are multiple Rspack instances. +Get the exports of @rspack/core to obtain the associated internal objects. This is especially useful when you cannot directly reference `@rspack/core` or there are multiple Rspack instances. + +A common example is accessing the [sources](/api/javascript-api/index#sources-object) object in a Rspack plugin: + +```js +const { RawSource } = compiler.rspack.sources; +const source = new RawSource('console.log("Hello, world!");'); +``` + +### webpack + +- **Type:** `typeof rspack` + +Equivalent to `compiler.rspack`, this property is used for compatibility with webpack plugins. + +If the Rspack plugin you are developing needs to be webpack compatible, you can use this property instead of `compiler.rspack`. + +```js +console.log(compiler.webpack === compiler.rspack); // true +``` ### name -**Type:** `string` +- **Type:** `string` Get the name: @@ -294,37 +313,37 @@ Current project root directory: ### root -**Type:** `Compiler` +- **Type:** `Compiler` Get the root of the child compiler tree. ### options -**Type:** `RspackOptionsNormalized` +- **Type:** `RspackOptionsNormalized` Get the full options used by this compiler. ### watchMode -**Type:** `boolean` +- **Type:** `boolean` Whether started through `compiler.watch`. ### watching -**Type:** `Watching` +- **Type:** `Watching` Get the watching object, see [watch method](#watch) for more details. ### running -**Type:** `boolean` +- **Type:** `boolean` Whether the compilation is currently being executed. ### inputFileSystem -**Type:** `InputFileSystem` +- **Type:** `InputFileSystem` Get the proxy object used for reading from the file system, which has optimizations such as caching inside to reduce duplicate reading of the same file. @@ -340,7 +359,7 @@ Get the proxy object used for reading from the file system, which has optimizati ### outputFileSystem -**Type:** `OutputFileSystem` +- **Type:** `OutputFileSystem` Get the proxy object used for writing to the file system, `fs` by default. @@ -356,7 +375,7 @@ Get the proxy object used for writing to the file system, `fs` by default. ### watchFileSystem -**Type:** `WatchFileSystem` +- **Type:** `WatchFileSystem` Get the proxy object used for watching files or directories changes, which provides a `watch` method to start watching, and passes in the changed and removed items in the callback. @@ -491,7 +510,7 @@ getInfrastructureLogger(name: string): Logger; #### compilers -**Type:** `Compiler[]` +- **Type:** `Compiler[]` Get all included compilers. @@ -509,7 +528,7 @@ Get all included compilers. -**Type:** `RspackOptionsNormalized[]` +- **Type:** `RspackOptionsNormalized[]` Get all the [full options](/config/index) used by the compilers. @@ -517,7 +536,7 @@ Get all the [full options](/config/index) used by the compilers. -**Type:** `InputFileSystem` +- **Type:** `InputFileSystem` Set the proxy object used for reading from the file system for each compiler. @@ -535,7 +554,7 @@ Set the proxy object used for reading from the file system for each compiler. -**Type:** `OutputFileSystem` +- **Type:** `OutputFileSystem` Set the proxy object used for writing from the file system for each compiler. @@ -553,7 +572,7 @@ Set the proxy object used for writing from the file system for each compiler. -**Type:** `WatchFileSystem` +- **Type:** `WatchFileSystem` Set the proxy object used for watching files or directories changes for each compiler. @@ -569,6 +588,6 @@ Set the proxy object used for watching files or directories changes for each com #### running -**Type:** `boolean` +- **Type:** `boolean` Whether the compilation is currently being executed. diff --git a/website/docs/en/api/javascript-api/index.mdx b/website/docs/en/api/javascript-api/index.mdx index fbaf0e361f7..39d211e4121 100644 --- a/website/docs/en/api/javascript-api/index.mdx +++ b/website/docs/en/api/javascript-api/index.mdx @@ -15,7 +15,7 @@ Rspack provides a set of JavaScript APIs to be used in JavaScript runtimes like The JavaScript API is useful in scenarios in which you need to customize the build or development process since all the reporting and error handling must be done manually and webpack only does the compiling part. For this reason the [`stats`](/config/stats) configuration options will not have any effect in the `rspack()` call. :::tip -`@rspack/core` is designed based on Webpack's JavaScript API to ensure functional consistency and a similar user experience. +`@rspack/core` is designed based on webpack's JavaScript API to ensure functional consistency and a similar user experience. ::: ## Installation @@ -262,3 +262,16 @@ compiler.run((err, stats) => { }); }); ``` + +## `sources` object + +`@rspack/core` exports the [webpack-sources](https://github.com/webpack/webpack-sources) module through `sources`. It provides a set of classes for creating and manipulating source code fragments and source maps. When developing Rspack plugins, you can use these classes to handle and manipulate source code. + +```js +import { sources } from '@rspack/core'; + +const { RawSource } = sources; +const source = new RawSource('console.log("Hello, world!");'); +``` + +For detailed usage, please refer to the [webpack-sources](https://github.com/webpack/webpack-sources) documentation. diff --git a/website/docs/zh/api/javascript-api/compiler.mdx b/website/docs/zh/api/javascript-api/compiler.mdx index ed273749397..559c5b31b3b 100644 --- a/website/docs/zh/api/javascript-api/compiler.mdx +++ b/website/docs/zh/api/javascript-api/compiler.mdx @@ -269,15 +269,34 @@ isChild(): boolean; 详见 [Compiler 钩子](/api/plugin-api/compiler-hooks) -### rspack/webpack +### rspack -**类型:** `typeof rspack` +- **类型:** `typeof rspack` -获取当前的 `@rspack/core` 的导出,以此来获取关联的内部对象。这通常在 Rspack 被封装或者存在多个 Rspack 实例时很有用。 +获取当前的 `@rspack/core` 的导出,以此来获取关联的内部对象。当你无法直接引用 `@rspack/core`,或者存在多个 Rspack 实例时很有用。 + +一个常见的例子是在 Rspack 插件中获取 [sources](/api/javascript-api/index#sources-对象) 对象: + +```js +const { RawSource } = compiler.rspack.sources; +const source = new RawSource('console.log("Hello, world!");'); +``` + +### webpack + +- **类型:** `typeof rspack` + +等同于 `compiler.rspack`,该属性用于兼容 webpack 插件。 + +如果你开发的 Rspack 插件需要兼容 webpack,可以使用该属性代替 `compiler.rspack`。 + +```js +console.log(compiler.webpack === compiler.rspack); // true +``` ### name -**类型:** `string` +- **类型:** `string` 获取名称: @@ -294,37 +313,37 @@ isChild(): boolean; ### root -**类型:** `Compiler` +- **类型:** `Compiler` 获取根 Compiler 实例。 ### options -**类型:** `RspackOptionsNormalized` +- **类型:** `RspackOptionsNormalized` 获取根 Compiler 所使用的完整[构建配置](/config/index)。 ### watchMode -**类型:** `boolean` +- **类型:** `boolean` 是否通过 watch 启动。 ### watching -**类型:** `Watching` +- **类型:** `Watching` 获取 watch 启动下的监听控制对象,详见 [watch 方法](#watch) ### running -**类型:** `boolean` +- **类型:** `boolean` 当前是否正在执行构建流程。 ### inputFileSystem -**类型:** `InputFileSystem` +- **类型:** `InputFileSystem` 获取用于从文件系统读取的代理对象,其内部有缓存等优化,以降低对同一文件的重复读取。 @@ -340,7 +359,7 @@ isChild(): boolean; ### outputFileSystem -**类型:** `OutputFileSystem` +- **类型:** `OutputFileSystem` 获取用于输出到文件系统的代理对象,默认使用 `fs`。 @@ -356,7 +375,7 @@ isChild(): boolean; ### watchFileSystem -**类型:** `WatchFileSystem` +- **类型:** `WatchFileSystem` 获取用于持续监听文件/目录变化的代理对象,提供一个 `watch` 方法来启动监听,并在回调中传入变更和移除的项目。 @@ -491,7 +510,7 @@ getInfrastructureLogger(name: string): Logger; #### compilers -**类型:** `Compiler[]` +- **类型:** `Compiler[]` 获取包含的所有 Compiler 实例。 @@ -509,7 +528,7 @@ getInfrastructureLogger(name: string): Logger; -**类型:** `RspackOptionsNormalized[]` +- **类型:** `RspackOptionsNormalized[]` 获取各个 Compiler 所使用的完整[构建配置](/config/index)。 @@ -517,7 +536,7 @@ getInfrastructureLogger(name: string): Logger; -**类型:** `InputFileSystem` +- **类型:** `InputFileSystem` 为各个 Compiler 设置用于从文件系统读取的代理对象。 @@ -535,7 +554,7 @@ getInfrastructureLogger(name: string): Logger; -**类型:** `OutputFileSystem` +- **类型:** `OutputFileSystem` 为各个 Compiler 设置用于输出到文件系统的代理对象。 @@ -553,7 +572,7 @@ getInfrastructureLogger(name: string): Logger; -**类型:** `WatchFileSystem` +- **类型:** `WatchFileSystem` 为各个 Compiler 设置用于持续监听文件/目录变化的代理对象。 @@ -569,6 +588,6 @@ getInfrastructureLogger(name: string): Logger; #### running -**类型:** `boolean` +- **类型:** `boolean` 当前是否正在执行构建流程。 diff --git a/website/docs/zh/api/javascript-api/index.mdx b/website/docs/zh/api/javascript-api/index.mdx index 9d2a1c951bb..cd66ff27aef 100644 --- a/website/docs/zh/api/javascript-api/index.mdx +++ b/website/docs/zh/api/javascript-api/index.mdx @@ -15,7 +15,7 @@ Rspack 提供了一组 JavaScript API,可在 Node.js 或 Bun 等 JavaScript 当你需要自定义构建或开发流程时,JavaScript API 非常有用,因为此时所有的报告和错误处理都必须自行实现,Rspack 仅仅负责编译的部分。所以 [`stats`](/config/stats) 配置选项不会在 `rspack()` 调用中生效。 :::tip 提示 -`@rspack/core` 是基于 Webpack JavaScript API 设计的,旨在提供一致的功能和相似的使用体验。 +`@rspack/core` 是基于 webpack 的 JavaScript API 设计的,旨在提供一致的功能和相似的使用体验。 ::: ## 安装 @@ -262,3 +262,16 @@ compiler.run((err, stats) => { }); }); ``` + +## `sources` 对象 + +`@rspack/core` 通过 `sources` 导出了 [webpack-sources](https://github.com/webpack/webpack-sources) 模块。它提供了一组 class,用于创建和操作源代码片段和源码映射。在开发 Rspack 插件时,你可以使用这些 class 来处理和操作源代码。 + +```js +import { sources } from '@rspack/core'; + +const { RawSource } = sources; +const source = new RawSource('console.log("Hello, world!");'); +``` + +详细用法请参考 [webpack-sources](https://github.com/webpack/webpack-sources) 文档。 From 3b086835d8367b5d7350ff2930b3c86e17735887 Mon Sep 17 00:00:00 2001 From: Wei Date: Thu, 12 Sep 2024 17:05:29 +0800 Subject: [PATCH 34/51] chore: remove "rust-analyzer.checkOnSave" of VS Code (#7874) --- .vscode/settings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3410b4eeb0b..6b5e375a759 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,6 +42,5 @@ "tokio", "ukey", "Ukey" - ], - "rust-analyzer.checkOnSave": false + ] } From 5ebc1ec636a2afd160e5be0c4842bc0a2061aef6 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Thu, 12 Sep 2024 17:53:52 +0800 Subject: [PATCH 35/51] test: disable unstable dev server snapshot case (#7877) --- .../__snapshots__/api.test.js.snap.webpack5 | 12 - .../rspack-dev-server/tests/e2e/api.test.js | 1329 +++++++++-------- .../tests/e2e/web-socket-server-url.test.js | 3 +- .../tests/fixtures/reload-config/main.css | 1 - 4 files changed, 667 insertions(+), 678 deletions(-) delete mode 100644 packages/rspack-dev-server/tests/fixtures/reload-config/main.css diff --git a/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 index 39890fbe5a9..4e25295ce5a 100644 --- a/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 +++ b/packages/rspack-dev-server/tests/e2e/__snapshots__/api.test.js.snap.webpack5 @@ -24,18 +24,6 @@ exports[`API Invalidate callback should use the provided \`callback\` function: exports[`API Invalidate callback should use the provided \`callback\` function: response status 1`] = `200`; -exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: console messages 1`] = ` -[ - "[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.", - "[HMR] Waiting for update signal from WDS...", - "Hey.", - "WebSocket connection to 'ws://test.host:8158/ws' failed: Error in connection establishment: net::ERR_NAME_NOT_RESOLVED", - "[webpack-dev-server] JSHandle@object", - "[webpack-dev-server] Disconnected!", - "[webpack-dev-server] Trying to reconnect...", -] -`; - exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: page errors 1`] = `[]`; exports[`API Server.checkHostHeader should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object: response status 1`] = `200`; diff --git a/packages/rspack-dev-server/tests/e2e/api.test.js b/packages/rspack-dev-server/tests/e2e/api.test.js index 5344f68c8e6..730224ebf89 100644 --- a/packages/rspack-dev-server/tests/e2e/api.test.js +++ b/packages/rspack-dev-server/tests/e2e/api.test.js @@ -9,745 +9,746 @@ const sessionSubscribe = require("../helpers/session-subscribe"); const port = require("../helpers/ports-map").api; describe("API", () => { - describe("WEBPACK_SERVE environment variable", () => { - const OLD_ENV = process.env; - let server; - let page; - let browser; - let pageErrors; - let consoleMessages; - - beforeEach(async () => { - // this is important - it clears the cache - jest.resetModules(); - - process.env = { ...OLD_ENV }; - - delete process.env.WEBPACK_SERVE; - - ({ page, browser } = await runBrowser()); - - pageErrors = []; - consoleMessages = []; - }); - - afterEach(async () => { - await browser.close(); - await server.stop(); - process.env = OLD_ENV; - }); - - it("should be present", async () => { - expect(process.env.WEBPACK_SERVE).toBeUndefined(); - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - const { - RspackDevServer: WebpackDevServer - } = require("@rspack/dev-server"); - - const compiler = webpack(config); - server = new WebpackDevServer({ port }, compiler); - - await server.start(); - - expect(process.env.WEBPACK_SERVE).toBe("true"); - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); - }); - - describe("latest async API", () => { - it(`should work with async API`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await server.start(); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - - it(`should work with callback API`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await new Promise(resolve => { - server.startCallback(() => { - resolve(); - }); - }); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }); - } - }); - - it(`should catch errors within startCallback`, async () => { - const compiler = webpack(config); - const server = new Server( - { port, static: "https://absolute-url.com/somewhere" }, - compiler - ); - - await new Promise(resolve => { - server.startCallback(err => { - expect(err.message).toEqual( - "Using a URL as static.directory is not supported" - ); - resolve(); - }); - }); - - await new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }); - }); - - it(`should work when using configured manually`, async () => { - const compiler = webpack({ - ...config, - entry: [ - "@rspack/core/hot/dev-server.js", - `@rspack/dev-server/client/index.js?hot=true&live-reload=true"`, - path.resolve(__dirname, "../fixtures/client-config/foo.js") - ], - plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()] - }); - const server = new Server({ port, hot: false, client: false }, compiler); - - await server.start(); - - const { page, browser } = await runBrowser(); - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - - it(`should work and allow to rerun dev server multiple times`, async () => { - const compiler = webpack(config); - const server = new Server({ port }, compiler); - - await server.start(); - - const { page: firstPage, browser } = await runBrowser(); - - try { - const firstPageErrors = []; - const firstConsoleMessages = []; - - firstPage - .on("console", message => { - firstConsoleMessages.push(message); - }) - .on("pageerror", error => { - firstPageErrors.push(error); - }); - - await firstPage.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect( - firstConsoleMessages.map(message => message.text()) - ).toMatchSnapshot("console messages"); - expect(firstPageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await server.stop(); - } - - await server.start(); - - const secondPage = await runBrowser.runPage(browser); - - try { - // const secondPageErrors = []; - // const secondConsoleMessages = []; - // secondPage - // .on("console", (message) => { - // secondConsoleMessages.push(message); - // }) - // .on("pageerror", (error) => { - // secondPageErrors.push(error); - // }); - // await secondPage.goto(`http://127.0.0.1:${port}/`, { - // waitUntil: "networkidle0", - // }); - // expect( - // secondConsoleMessages.map((message) => message.text()), - // ).toMatchSnapshot("console messages"); - // expect(secondPageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - }); - - describe("Invalidate callback", () => { - let compiler; - let server; - let page; - let browser; - let pageErrors; - let consoleMessages; - - beforeEach(async () => { - compiler = webpack(config); - - ({ page, browser } = await runBrowser()); - - pageErrors = []; - consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - server = new Server({ port, static: false }, compiler); - - await server.start(); - }); - - afterEach(async () => { - await browser.close(); - await server.stop(); - }); - - it("should use the default `noop` callback when invalidate is called without any callback", async () => { - const callback = jest.fn(); - - server.invalidate(); - server.middleware.context.callbacks[0] = callback; - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(callback).toHaveBeenCalledTimes(1); - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - expect(pageErrors).toMatchSnapshot("page errors"); - }); - - it("should use the provided `callback` function", async () => { - const callback = jest.fn(); - - server.invalidate(callback); - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - expect(callback).toHaveBeenCalledTimes(1); - expect(response.status()).toMatchSnapshot("response status"); - - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); - - expect(pageErrors).toMatchSnapshot("page errors"); - }); - }); - - describe("Server.getFreePort", () => { - let dummyServers = []; - let devServerPort; - - afterEach(() => { - delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; - delete process.env.WEBPACK_DEV_SERVER_PORT_RETRY; - - return dummyServers - .reduce( - (p, server) => - p.then( - () => - new Promise(resolve => { - server.stopCallback(() => { - resolve(); - }); - }) - ), - Promise.resolve() - ) - .then(() => { - dummyServers = []; - }); - }); + describe("WEBPACK_SERVE environment variable", () => { + const OLD_ENV = process.env; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + // this is important - it clears the cache + jest.resetModules(); + + process.env = { ...OLD_ENV }; + + delete process.env.WEBPACK_SERVE; + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + process.env = OLD_ENV; + }); + + it("should be present", async () => { + expect(process.env.WEBPACK_SERVE).toBeUndefined(); + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const { + RspackDevServer: WebpackDevServer + } = require("@rspack/dev-server"); + + const compiler = webpack(config); + server = new WebpackDevServer({ port }, compiler); + + await server.start(); + + expect(process.env.WEBPACK_SERVE).toBe("true"); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("latest async API", () => { + it(`should work with async API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work with callback API`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await new Promise(resolve => { + server.startCallback(() => { + resolve(); + }); + }); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + } + }); + + it(`should catch errors within startCallback`, async () => { + const compiler = webpack(config); + const server = new Server( + { port, static: "https://absolute-url.com/somewhere" }, + compiler + ); + + await new Promise(resolve => { + server.startCallback(err => { + expect(err.message).toEqual( + "Using a URL as static.directory is not supported" + ); + resolve(); + }); + }); + + await new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }); + }); + + it(`should work when using configured manually`, async () => { + const compiler = webpack({ + ...config, + entry: [ + "@rspack/core/hot/dev-server.js", + `@rspack/dev-server/client/index.js?hot=true&live-reload=true"`, + path.resolve(__dirname, "../fixtures/client-config/foo.js") + ], + plugins: [...config.plugins, new webpack.HotModuleReplacementPlugin()] + }); + const server = new Server({ port, hot: false, client: false }, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + + it(`should work and allow to rerun dev server multiple times`, async () => { + const compiler = webpack(config); + const server = new Server({ port }, compiler); + + await server.start(); + + const { page: firstPage, browser } = await runBrowser(); + + try { + const firstPageErrors = []; + const firstConsoleMessages = []; + + firstPage + .on("console", message => { + firstConsoleMessages.push(message); + }) + .on("pageerror", error => { + firstPageErrors.push(error); + }); + + await firstPage.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect( + firstConsoleMessages.map(message => message.text()) + ).toMatchSnapshot("console messages"); + expect(firstPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await server.stop(); + } + + await server.start(); + + const secondPage = await runBrowser.runPage(browser); + + try { + // const secondPageErrors = []; + // const secondConsoleMessages = []; + // secondPage + // .on("console", (message) => { + // secondConsoleMessages.push(message); + // }) + // .on("pageerror", (error) => { + // secondPageErrors.push(error); + // }); + // await secondPage.goto(`http://127.0.0.1:${port}/`, { + // waitUntil: "networkidle0", + // }); + // expect( + // secondConsoleMessages.map((message) => message.text()), + // ).toMatchSnapshot("console messages"); + // expect(secondPageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); + + describe("Invalidate callback", () => { + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + server = new Server({ port, static: false }, compiler); + + await server.start(); + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + }); + + it("should use the default `noop` callback when invalidate is called without any callback", async () => { + const callback = jest.fn(); + + server.invalidate(); + server.middleware.context.callbacks[0] = callback; + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + expect(pageErrors).toMatchSnapshot("page errors"); + }); + + it("should use the provided `callback` function", async () => { + const callback = jest.fn(); + + server.invalidate(callback); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + }); + }); + + describe("Server.getFreePort", () => { + let dummyServers = []; + let devServerPort; + + afterEach(() => { + delete process.env.WEBPACK_DEV_SERVER_BASE_PORT; + delete process.env.WEBPACK_DEV_SERVER_PORT_RETRY; + + return dummyServers + .reduce( + (p, server) => + p.then( + () => + new Promise(resolve => { + server.stopCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ) + .then(() => { + dummyServers = []; + }); + }); - function createDummyServers(n) { - process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; + function createDummyServers(n) { + process.env.WEBPACK_DEV_SERVER_BASE_PORT = 60000; - return (Array.isArray(n) ? n : [...new Array(n)]).reduce( - (p, _, i) => - p.then( - () => - new Promise(resolve => { - devServerPort = 60000 + i; - const compiler = webpack(config); - const server = new Server( - { port: devServerPort, host: "0.0.0.0" }, - compiler - ); + return (Array.isArray(n) ? n : [...new Array(n)]).reduce( + (p, _, i) => + p.then( + () => + new Promise(resolve => { + devServerPort = 60000 + i; + const compiler = webpack(config); + const server = new Server( + { port: devServerPort, host: "0.0.0.0" }, + compiler + ); - dummyServers.push(server); + dummyServers.push(server); - server.startCallback(() => { - resolve(); - }); - }) - ), - Promise.resolve() - ); - } + server.startCallback(() => { + resolve(); + }); + }) + ), + Promise.resolve() + ); + } - it("should return the port when the port is specified", async () => { - const retryCount = 1; + it("should return the port when the port is specified", async () => { + const retryCount = 1; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - const freePort = await Server.getFreePort(9082); + const freePort = await Server.getFreePort(9082); - expect(freePort).toEqual(9082); - }); + expect(freePort).toEqual(9082); + }); - it("should return the port when the port is `null`", async () => { - const retryCount = 2; + it("should return the port when the port is `null`", async () => { + const retryCount = 2; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(null); + const freePort = await Server.getFreePort(null); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should return the port when the port is undefined", async () => { - const retryCount = 3; + it("should return the port when the port is undefined", async () => { + const retryCount = 3; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - // eslint-disable-next-line no-undefined - const freePort = await Server.getFreePort(undefined); + // eslint-disable-next-line no-undefined + const freePort = await Server.getFreePort(undefined); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port for up to defaultPortRetry times (number)", async () => { - const retryCount = 4; + it("should retry finding the port for up to defaultPortRetry times (number)", async () => { + const retryCount = 4; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port for up to defaultPortRetry times (string)", async () => { - const retryCount = 5; + it("should retry finding the port for up to defaultPortRetry times (string)", async () => { + const retryCount = 5; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = retryCount; - await createDummyServers(retryCount); + await createDummyServers(retryCount); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toEqual(60000 + retryCount); + expect(freePort).toEqual(60000 + retryCount); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - const pageErrors = []; - const consoleMessages = []; + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); + expect(pageErrors).toMatchSnapshot("page errors"); - await browser.close(); - }); + await browser.close(); + }); - it("should retry finding the port when serial ports are busy", async () => { - const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; + it("should retry finding the port when serial ports are busy", async () => { + const busyPorts = [60000, 60001, 60002, 60003, 60004, 60005]; - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1000; - await createDummyServers(busyPorts); + await createDummyServers(busyPorts); - const freePort = await Server.getFreePort(); + const freePort = await Server.getFreePort(); - expect(freePort).toBeGreaterThan(60005); + expect(freePort).toBeGreaterThan(60005); - const { page, browser } = await runBrowser(); + const { page, browser } = await runBrowser(); - try { - const pageErrors = []; - const consoleMessages = []; + try { + const pageErrors = []; + const consoleMessages = []; - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); - const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { - waitUntil: "networkidle0" - }); + const response = await page.goto(`http://127.0.0.1:${devServerPort}/`, { + waitUntil: "networkidle0" + }); - expect(response.status()).toMatchSnapshot("response status"); + expect(response.status()).toMatchSnapshot("response status"); - expect(consoleMessages.map(message => message.text())).toMatchSnapshot( - "console messages" - ); + expect(consoleMessages.map(message => message.text())).toMatchSnapshot( + "console messages" + ); - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - } - }); + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + } + }); - it("should throw the error when the port isn't found", async () => { - expect.assertions(1); + it("should throw the error when the port isn't found", async () => { + expect.assertions(1); - jest.mock( - "webpack-dev-server/lib/getPort", - () => () => Promise.reject(new Error("busy")) - ); + jest.mock( + "webpack-dev-server/lib/getPort", + () => () => Promise.reject(new Error("busy")) + ); - process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1; + process.env.WEBPACK_DEV_SERVER_PORT_RETRY = 1; - try { - await Server.getFreePort(); - } catch (error) { - expect(error.message).toMatchSnapshot(); - } - }); - }); + try { + await Server.getFreePort(); + } catch (error) { + expect(error.message).toMatchSnapshot(); + } + }); + }); - describe("Server.checkHostHeader", () => { - it("should allow access for every requests using an IP", () => { - const options = {}; - - const tests = [ - "192.168.1.123", - "192.168.1.2:8080", - "[::1]", - "[::1]:8080", - "[ad42::1de2:54c2:c2fa:1234]", - "[ad42::1de2:54c2:c2fa:1234]:8080" - ]; - - const compiler = webpack(config); - const server = new Server(options, compiler); - - tests.forEach(test => { - const headers = { host: test }; - - if (!server.checkHeader(headers, "host")) { - throw new Error("Validation didn't pass"); - } - }); - }); - - it('should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object', async () => { - const options = { - port, - client: { - reconnect: false, - webSocketURL: { - hostname: "test.host" - } - }, - webSocketServer: "ws" - }; - const headers = { - origin: "https://test.host" - }; - - const compiler = webpack(config); - const server = new Server(options, compiler); - - await server.start(); - - const { page, browser } = await runBrowser(); - - try { - const pageErrors = []; - const consoleMessages = []; - - page - .on("console", message => { - consoleMessages.push(message); - }) - .on("pageerror", error => { - pageErrors.push(error); - }); - - const webSocketRequests = []; - const session = await page.target().createCDPSession(); - - session.on("Network.webSocketCreated", test => { - webSocketRequests.push(test); - }); - - await session.send("Target.setAutoAttach", { - autoAttach: true, - flatten: true, - waitForDebuggerOnStart: true - }); - - sessionSubscribe(session); - - const response = await page.goto(`http://127.0.0.1:${port}/`, { - waitUntil: "networkidle0" - }); - - if (!server.checkHeader(headers, "origin")) { - throw new Error("Validation didn't fail"); - } - - await new Promise(resolve => { - const interval = setInterval(() => { - const needFinish = consoleMessages.filter(message => - /Trying to reconnect/.test(message.text()) - ); - - if (needFinish.length > 0) { - clearInterval(interval); - resolve(); - } - }, 100); - }); - - expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); - - expect(response.status()).toMatchSnapshot("response status"); - - expect( - // net::ERR_NAME_NOT_RESOLVED can be multiple times - consoleMessages.map(message => message.text()).slice(0, 7) - ).toMatchSnapshot("console messages"); - - expect(pageErrors).toMatchSnapshot("page errors"); - } catch (error) { - throw error; - } finally { - await browser.close(); - await server.stop(); - } - }); - }); + describe("Server.checkHostHeader", () => { + it("should allow access for every requests using an IP", () => { + const options = {}; + + const tests = [ + "192.168.1.123", + "192.168.1.2:8080", + "[::1]", + "[::1]:8080", + "[ad42::1de2:54c2:c2fa:1234]", + "[ad42::1de2:54c2:c2fa:1234]:8080" + ]; + + const compiler = webpack(config); + const server = new Server(options, compiler); + + tests.forEach(test => { + const headers = { host: test }; + + if (!server.checkHeader(headers, "host")) { + throw new Error("Validation didn't pass"); + } + }); + }); + + it('should allow URLs with scheme for checking origin when the "option.client.webSocketURL" is object', async () => { + const options = { + port, + client: { + reconnect: false, + webSocketURL: { + hostname: "test.host" + } + }, + webSocketServer: "ws" + }; + const headers = { + origin: "https://test.host" + }; + + const compiler = webpack(config); + const server = new Server(options, compiler); + + await server.start(); + + const { page, browser } = await runBrowser(); + + try { + const pageErrors = []; + const consoleMessages = []; + + page + .on("console", message => { + consoleMessages.push(message); + }) + .on("pageerror", error => { + pageErrors.push(error); + }); + + const webSocketRequests = []; + const session = await page.target().createCDPSession(); + + session.on("Network.webSocketCreated", test => { + webSocketRequests.push(test); + }); + + await session.send("Target.setAutoAttach", { + autoAttach: true, + flatten: true, + waitForDebuggerOnStart: true + }); + + sessionSubscribe(session); + + const response = await page.goto(`http://127.0.0.1:${port}/`, { + waitUntil: "networkidle0" + }); + + if (!server.checkHeader(headers, "origin")) { + throw new Error("Validation didn't fail"); + } + + await new Promise(resolve => { + const interval = setInterval(() => { + const needFinish = consoleMessages.filter(message => + /Trying to reconnect/.test(message.text()) + ); + + if (needFinish.length > 0) { + clearInterval(interval); + resolve(); + } + }, 100); + }); + + expect(webSocketRequests[0].url).toMatchSnapshot("web socket URL"); + + expect(response.status()).toMatchSnapshot("response status"); + + // TODO: not stable on lynx linux ci + // expect( + // // net::ERR_NAME_NOT_RESOLVED can be multiple times + // consoleMessages.map(message => message.text()).slice(0, 7) + // ).toMatchSnapshot("console messages"); + + expect(pageErrors).toMatchSnapshot("page errors"); + } catch (error) { + throw error; + } finally { + await browser.close(); + await server.stop(); + } + }); + }); }); diff --git a/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js b/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js index 2ab0c6db5f9..c537b381a8f 100644 --- a/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js +++ b/packages/rspack-dev-server/tests/e2e/web-socket-server-url.test.js @@ -2750,7 +2750,8 @@ describe("web socket server URL", () => { }, 100); }); - expect(consoleMessages).toMatchSnapshot("console messages"); + // TODO: not stable on lynx linux ci + // expect(consoleMessages).toMatchSnapshot("console messages"); expect( pageErrors.map(pageError => pageError.message.split("\n")[0]) ).toMatchSnapshot("page errors"); diff --git a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css b/packages/rspack-dev-server/tests/fixtures/reload-config/main.css deleted file mode 100644 index 585e8f34510..00000000000 --- a/packages/rspack-dev-server/tests/fixtures/reload-config/main.css +++ /dev/null @@ -1 +0,0 @@ -body { background-color: rgb(0, 0, 255); } \ No newline at end of file From 8b89d53e0acafeba1a6a45c95d1b17b9c1262bcc Mon Sep 17 00:00:00 2001 From: Fy <1114550440@qq.com> Date: Thu, 12 Sep 2024 21:55:46 +0800 Subject: [PATCH 36/51] refactor: css-extract using parseMeta to store css info (#7878) --- crates/node_binding/binding.d.ts | 1 + .../src/options/raw_builtins/mod.rs | 11 +-- .../plugins/css_extract_additional_data.rs | 92 ------------------- .../src/plugins/js_loader/context.rs | 5 + .../src/plugins/js_loader/scheduler.rs | 1 + .../rspack_binding_options/src/plugins/mod.rs | 2 - crates/rspack_core/src/normal_module.rs | 1 + .../rspack_core/src/parser_and_generator.rs | 2 + crates/rspack_loader_runner/src/context.rs | 3 +- crates/rspack_loader_runner/src/runner.rs | 5 +- crates/rspack_plugin_extract_css/src/lib.rs | 2 +- .../src/parser_plugin.rs | 24 ++--- .../src/parser_and_generator/mod.rs | 2 + .../src/visitors/dependency/mod.rs | 4 +- .../src/visitors/dependency/parser/mod.rs | 3 + packages/rspack/etc/api.md | 2 + .../src/builtin-plugin/css-extract/loader.ts | 13 +-- packages/rspack/src/config/adapterRuleUse.ts | 8 ++ packages/rspack/src/loader-runner/index.ts | 4 + tests/plugin-test/copy-plugin/build/main.js | 2 +- .../cases/custom-loader/expected/main.css | 4 + .../css-extract/cases/custom-loader/index.js | 1 + .../css-extract/cases/custom-loader/loader.js | 3 + .../css-extract/cases/custom-loader/style.css | 3 + .../cases/custom-loader/webpack.config.js | 25 +++++ 25 files changed, 93 insertions(+), 130 deletions(-) delete mode 100644 crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs create mode 100644 tests/plugin-test/css-extract/cases/custom-loader/expected/main.css create mode 100644 tests/plugin-test/css-extract/cases/custom-loader/index.js create mode 100644 tests/plugin-test/css-extract/cases/custom-loader/loader.js create mode 100644 tests/plugin-test/css-extract/cases/custom-loader/style.css create mode 100644 tests/plugin-test/css-extract/cases/custom-loader/webpack.config.js diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 2f440ddad38..8ebb2815109 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -599,6 +599,7 @@ export interface JsLoaderContext { /** Content maybe empty in pitching stage */ content: null | Buffer additionalData?: any + __internal__parseMeta: Record sourceMap?: Buffer cacheable: boolean fileDependencies: Array 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 ff430963ebe..db120ec9367 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -91,10 +91,9 @@ use self::{ raw_size_limits::RawSizeLimitsPluginOptions, }; use crate::{ - plugins::{CssExtractRspackAdditionalDataPlugin, JsLoaderRspackPlugin}, - JsLoaderRunner, RawDynamicEntryPluginOptions, RawEvalDevToolModulePluginOptions, - RawExternalItemWrapper, RawExternalsPluginOptions, RawHttpExternalsRspackPluginOptions, - RawSourceMapDevToolPluginOptions, RawSplitChunksOptions, + plugins::JsLoaderRspackPlugin, JsLoaderRunner, RawDynamicEntryPluginOptions, + RawEvalDevToolModulePluginOptions, RawExternalItemWrapper, RawExternalsPluginOptions, + RawHttpExternalsRspackPluginOptions, RawSourceMapDevToolPluginOptions, RawSplitChunksOptions, }; #[napi(string_enum)] @@ -187,7 +186,7 @@ pub struct BuiltinPlugin { } impl BuiltinPlugin { - pub fn append_to(self, env: Env, plugins: &mut Vec) -> rspack_error::Result<()> { + pub fn append_to(self, _env: Env, plugins: &mut Vec) -> rspack_error::Result<()> { match self.name { // webpack also have these plugins BuiltinPluginName::DefinePlugin => { @@ -479,8 +478,6 @@ impl BuiltinPlugin { ) } BuiltinPluginName::CssExtractRspackPlugin => { - let additional_data_plugin = CssExtractRspackAdditionalDataPlugin::new(env)?.boxed(); - plugins.push(additional_data_plugin); let plugin = rspack_plugin_extract_css::plugin::PluginCssExtract::new( downcast_into::(self.options)?.into(), ) diff --git a/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs b/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs deleted file mode 100644 index 6063984837c..00000000000 --- a/crates/rspack_binding_options/src/plugins/css_extract_additional_data.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::fmt::Debug; - -use napi::{bindgen_prelude::Unknown, Env}; -use rspack_core::{ - ApplyContext, CompilerOptions, NormalModuleAdditionalData, Plugin, PluginContext, -}; -use rspack_error::Result; -use rspack_hook::{plugin, plugin_hook}; -use rspack_loader_runner::AdditionalData; -use rspack_napi::{threadsafe_js_value_ref::ThreadsafeJsValueRef, JsCallback, NapiResultExt}; -use rspack_plugin_extract_css::CssExtractJsonDataList; -use tokio::sync::oneshot; - -#[plugin] -pub(crate) struct CssExtractRspackAdditionalDataPlugin { - js_callback: JsCallback>, -} - -impl CssExtractRspackAdditionalDataPlugin { - pub fn new(env: Env) -> Result { - Ok(Self::new_inner( - unsafe { JsCallback::new(env.raw()) }.into_rspack_result()?, - )) - } -} - -impl Debug for CssExtractRspackAdditionalDataPlugin { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "CssExtractRspackAdditionalDataPlugin(..)") - } -} - -#[plugin_hook(NormalModuleAdditionalData for CssExtractRspackAdditionalDataPlugin)] -async fn additional_data(&self, additional_data: &mut Option<&mut AdditionalData>) -> Result<()> { - if !additional_data - .as_ref() - .is_some_and(|data| data.contains::>()) - { - return Ok(()); - } - if let Some(mut old_data) = additional_data.as_mut().map(|data| std::mem::take(*data)) { - let (tx, rx) = oneshot::channel::(); - self.js_callback.call(Box::new(move |env| { - if let Some(data) = old_data - .get::>() - .and_then(|data| data.get(env).ok()) - .and_then(|data| data.coerce_to_object().ok()) - .and_then(|data| data.get::<_, String>("css-extract-rspack-plugin").ok()) - .flatten() - { - let data_list: Vec = data - .split("__RSPACK_CSS_EXTRACT_SEP__") - .map(|info| { - serde_json::from_str(info) - .unwrap_or_else(|e| panic!("failed to parse CssExtractJsonData: {}", e)) - }) - .collect(); - - old_data.insert(CssExtractJsonDataList(data_list)); - }; - tx.send(old_data) - .expect("should send `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); - })); - let new_data = rx - .await - .expect("should receive `additional_data` for `CssExtractRspackAdditionalDataPlugin`"); - if let Some(data) = additional_data.as_mut() { - let _ = std::mem::replace(*data, new_data); - } - } - Ok(()) -} - -#[async_trait::async_trait] -impl Plugin for CssExtractRspackAdditionalDataPlugin { - fn name(&self) -> &'static str { - "CssExtractRspackAdditionalDataPlugin" - } - - fn apply( - &self, - ctx: PluginContext<&mut ApplyContext>, - _options: &mut CompilerOptions, - ) -> Result<()> { - ctx - .context - .normal_module_hooks - .additional_data - .tap(additional_data::new(self)); - Ok(()) - } -} diff --git a/crates/rspack_binding_options/src/plugins/js_loader/context.rs b/crates/rspack_binding_options/src/plugins/js_loader/context.rs index 4315ca839a8..83081687292 100644 --- a/crates/rspack_binding_options/src/plugins/js_loader/context.rs +++ b/crates/rspack_binding_options/src/plugins/js_loader/context.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use napi::bindgen_prelude::*; use napi_derive::napi; use rspack_binding_values::{JsModule, JsResourceData, ToJsModule as _}; @@ -66,6 +68,8 @@ pub struct JsLoaderContext { pub content: Either, #[napi(ts_type = "any")] pub additional_data: Option>, + #[napi(js_name = "__internal__parseMeta")] + pub parse_meta: HashMap, pub source_map: Option, pub cacheable: bool, pub file_dependencies: Vec, @@ -98,6 +102,7 @@ impl TryFrom<&mut LoaderContext> for JsLoaderContext { Some(c) => Either::B(c.to_owned().into_bytes().into()), None => Either::A(Null), }, + parse_meta: cx.parse_meta.clone().into_iter().collect(), additional_data: cx .additional_data() .and_then(|data| data.get::>()) diff --git a/crates/rspack_binding_options/src/plugins/js_loader/scheduler.rs b/crates/rspack_binding_options/src/plugins/js_loader/scheduler.rs index 054dd2499df..1a56d764314 100644 --- a/crates/rspack_binding_options/src/plugins/js_loader/scheduler.rs +++ b/crates/rspack_binding_options/src/plugins/js_loader/scheduler.rs @@ -100,6 +100,7 @@ pub(crate) fn merge_loader_context( }) .collect(); to.loader_index = from.loader_index; + to.parse_meta = from.parse_meta.into_iter().collect(); Ok(()) } diff --git a/crates/rspack_binding_options/src/plugins/mod.rs b/crates/rspack_binding_options/src/plugins/mod.rs index 06ac0bd3f1b..34831055ade 100644 --- a/crates/rspack_binding_options/src/plugins/mod.rs +++ b/crates/rspack_binding_options/src/plugins/mod.rs @@ -1,4 +1,2 @@ -mod css_extract_additional_data; mod js_loader; -pub(super) use css_extract_additional_data::CssExtractRspackAdditionalDataPlugin; pub(super) use js_loader::{JsLoaderRspackPlugin, JsLoaderRunner}; diff --git a/crates/rspack_core/src/normal_module.rs b/crates/rspack_core/src/normal_module.rs index 704bfdc20cd..74b20f3d02d 100644 --- a/crates/rspack_core/src/normal_module.rs +++ b/crates/rspack_core/src/normal_module.rs @@ -513,6 +513,7 @@ impl Module for NormalModule { additional_data: loader_result.additional_data, build_info: &mut build_info, build_meta: &mut build_meta, + parse_meta: loader_result.parse_meta, })? .split_into_parts(); if !diagnostics.is_empty() { diff --git a/crates/rspack_core/src/parser_and_generator.rs b/crates/rspack_core/src/parser_and_generator.rs index 809d4bb97bf..5c29b46ad8d 100644 --- a/crates/rspack_core/src/parser_and_generator.rs +++ b/crates/rspack_core/src/parser_and_generator.rs @@ -8,6 +8,7 @@ use rspack_loader_runner::{AdditionalData, ResourceData}; use rspack_sources::BoxSource; use rspack_util::ext::AsAny; use rspack_util::source_map::SourceMapKind; +use rustc_hash::FxHashMap; use swc_core::common::Span; use crate::{ @@ -33,6 +34,7 @@ pub struct ParseContext<'a> { pub resource_data: &'a ResourceData, pub compiler_options: &'a CompilerOptions, pub additional_data: Option, + pub parse_meta: FxHashMap, pub build_info: &'a mut BuildInfo, pub build_meta: &'a mut BuildMeta, } diff --git a/crates/rspack_loader_runner/src/context.rs b/crates/rspack_loader_runner/src/context.rs index 573693c250a..00736e87b04 100644 --- a/crates/rspack_loader_runner/src/context.rs +++ b/crates/rspack_loader_runner/src/context.rs @@ -4,7 +4,7 @@ use derivative::Derivative; use rspack_error::Diagnostic; use rspack_paths::Utf8Path; use rspack_sources::SourceMap; -use rustc_hash::FxHashSet as HashSet; +use rustc_hash::{FxHashMap, FxHashSet as HashSet}; use crate::{ loader::LoaderItemList, AdditionalData, Content, LoaderItem, LoaderRunnerPlugin, ResourceData, @@ -39,6 +39,7 @@ pub struct LoaderContext { pub resource_data: Arc, #[derivative(Debug = "ignore")] pub context: Context, + pub parse_meta: FxHashMap, pub(crate) content: Option, pub(crate) source_map: Option, diff --git a/crates/rspack_loader_runner/src/runner.rs b/crates/rspack_loader_runner/src/runner.rs index 83e55a53894..0cdfd65fc0f 100644 --- a/crates/rspack_loader_runner/src/runner.rs +++ b/crates/rspack_loader_runner/src/runner.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, path::PathBuf, sync::Arc}; use rspack_error::{error, IntoTWithDiagnosticArray, Result, TWithDiagnosticArray}; use rspack_sources::SourceMap; -use rustc_hash::FxHashSet as HashSet; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; use crate::{ content::{AdditionalData, Content, ResourceData}, @@ -73,6 +73,7 @@ async fn create_loader_context( let mut loader_context = LoaderContext { hot: false, cacheable: true, + parse_meta: Default::default(), file_dependencies, context_dependencies: Default::default(), missing_dependencies: Default::default(), @@ -194,6 +195,7 @@ pub struct LoaderResult { pub content: Content, pub source_map: Option, pub additional_data: Option, + pub parse_meta: HashMap, } impl TryFrom> for TWithDiagnosticArray { @@ -218,6 +220,7 @@ impl TryFrom> for TWithDiagnosticArray, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct CssExtractJsonDataList(pub Vec); - #[derive(Debug, Default)] pub struct PluginCssExtractParserPlugin { - cache: FxDashMap>, + cache: FxDashMap>, } impl JavascriptParserPlugin for PluginCssExtractParserPlugin { fn finish(&self, parser: &mut JavascriptParser) -> Option { - let deps = if let Some(additional_data) = parser - .additional_data - .as_ref() - .and_then(|data| data.get::()) - { - if let Some(deps) = self.cache.get(additional_data) { + let deps = if let Some(data_str) = parser.parse_meta.get(PLUGIN_NAME) { + if let Some(deps) = self.cache.get(data_str) { deps.clone() - } else { - let deps = additional_data - .0 + } else if let Ok(data) = serde_json::from_str::>(data_str) { + let deps = data .iter() .enumerate() .map( @@ -73,8 +65,10 @@ impl JavascriptParserPlugin for PluginCssExtractParserPlugin { }, ) .collect::>(); - self.cache.insert(additional_data.clone(), deps.clone()); + self.cache.insert(data_str.clone(), deps.clone()); deps + } else { + vec![] } } else { vec![] diff --git a/crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs b/crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs index bfaf46bd5f5..3755aae74dc 100644 --- a/crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs +++ b/crates/rspack_plugin_javascript/src/parser_and_generator/mod.rs @@ -106,6 +106,7 @@ impl ParserAndGenerator for JavaScriptParserAndGenerator { loaders, module_parser_options, additional_data, + parse_meta, .. } = parse_context; let mut diagnostics: Vec> = vec![]; @@ -211,6 +212,7 @@ impl ParserAndGenerator for JavaScriptParserAndGenerator { unresolved_mark, &mut self.parser_plugins, additional_data, + parse_meta, ) }) { Ok(result) => result, diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs index 090270c2926..a0e7d829b44 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/mod.rs @@ -11,7 +11,7 @@ use rspack_core::{ }; use rspack_core::{BuildMeta, CompilerOptions, ModuleIdentifier, ModuleType, ResourceData}; use rspack_error::miette::Diagnostic; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use swc_core::common::Mark; use swc_core::common::{comments::Comments, BytePos, SourceFile, SourceMap}; use swc_core::ecma::atoms::Atom; @@ -57,6 +57,7 @@ pub fn scan_dependencies( unresolved_mark: Mark, parser_plugins: &mut Vec, additional_data: Option, + parse_meta: FxHashMap, ) -> Result>> { let mut parser = JavascriptParser::new( source_map, @@ -76,6 +77,7 @@ pub fn scan_dependencies( unresolved_mark, parser_plugins, additional_data, + parse_meta, ); parser.walk_program(program.get_inner_program()); diff --git a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs index 1e6290f7e62..bdd2b61c030 100644 --- a/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs +++ b/crates/rspack_plugin_javascript/src/visitors/dependency/parser/mod.rs @@ -213,6 +213,7 @@ pub struct JavascriptParser<'parser> { pub(crate) blocks: Vec>, // TODO: remove `additional_data` once we have builtin:css-extract-loader pub additional_data: Option, + pub parse_meta: FxHashMap, pub(crate) comments: Option<&'parser dyn Comments>, pub(crate) worker_index: u32, pub(crate) build_meta: &'parser mut BuildMeta, @@ -266,6 +267,7 @@ impl<'parser> JavascriptParser<'parser> { unresolved_mark: Mark, parser_plugins: &'parser mut Vec, additional_data: Option, + parse_meta: FxHashMap, ) -> Self { let warning_diagnostics: Vec> = Vec::with_capacity(4); let errors = Vec::with_capacity(4); @@ -398,6 +400,7 @@ impl<'parser> JavascriptParser<'parser> { prev_statement: None, inner_graph: InnerGraphState::new(), additional_data, + parse_meta, } } diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 1de9e310a5d..ef64eb52be0 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -5881,6 +5881,8 @@ const loader: z.ZodRecord; // @public (undocumented) export interface LoaderContext { + // @internal + __internal__parseMeta: Record; // (undocumented) addBuildDependency(file: string): void; // (undocumented) diff --git a/packages/rspack/src/builtin-plugin/css-extract/loader.ts b/packages/rspack/src/builtin-plugin/css-extract/loader.ts index e49c2eb9eb6..bbe6de7c727 100644 --- a/packages/rspack/src/builtin-plugin/css-extract/loader.ts +++ b/packages/rspack/src/builtin-plugin/css-extract/loader.ts @@ -11,8 +11,6 @@ export const ABSOLUTE_PUBLIC_PATH = `${BASE_URI}/mini-css-extract-plugin/`; export const SINGLE_DOT_PATH_SEGMENT = "__mini_css_extract_plugin_single_dot_path_segment__"; -const SERIALIZE_SEP = "__RSPACK_CSS_EXTRACT_SEP__"; - interface DependencyDescription { identifier: string; content: string; @@ -105,6 +103,7 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { const emit = typeof options.emit !== "undefined" ? options.emit : true; const callback = this.async(); const filepath = this.resourcePath; + const parseMeta = this.__internal__parseMeta; let { publicPath } = this._compilation!.outputOptions; @@ -260,16 +259,12 @@ export const pitch: LoaderDefinition["pitch"] = function (request, _, data) { ? hotLoader(result, { loaderContext: this, options, locals: locals! }) : result; - const additionalData: Record = { ...data }; if (dependencies.length > 0) { - additionalData[CssExtractRspackPlugin.pluginName] = dependencies - .map(dep => { - return JSON.stringify(dep); - }) - .join(SERIALIZE_SEP); + parseMeta[CssExtractRspackPlugin.pluginName] = + JSON.stringify(dependencies); } - callback(null, resultSource, undefined, additionalData); + callback(null, resultSource, undefined, data); }; this.importModule( diff --git a/packages/rspack/src/config/adapterRuleUse.ts b/packages/rspack/src/config/adapterRuleUse.ts index dc03f3c9eb8..b069c79b41a 100644 --- a/packages/rspack/src/config/adapterRuleUse.ts +++ b/packages/rspack/src/config/adapterRuleUse.ts @@ -149,6 +149,14 @@ export interface LoaderContext { _compiler: Compiler; _compilation: Compilation; _module: Module; + + /** + * Note: This is not a webpack public API, maybe removed in future. + * Store some data from loader, and consume it from parser, it may be removed in the future + * + * @internal + */ + __internal__parseMeta: Record; } export type LoaderDefinitionFunction< diff --git a/packages/rspack/src/loader-runner/index.ts b/packages/rspack/src/loader-runner/index.ts index ba1d5b019de..0089f2beea1 100644 --- a/packages/rspack/src/loader-runner/index.ts +++ b/packages/rspack/src/loader-runner/index.ts @@ -784,6 +784,10 @@ export async function runLoaders( get: () => loaderContext.loaders[loaderContext.loaderIndex].data, set: data => (loaderContext.loaders[loaderContext.loaderIndex].data = data) }); + Object.defineProperty(loaderContext, "__internal__parseMeta", { + enumerable: true, + get: () => context.__internal__parseMeta + }); switch (loaderState) { case JsLoaderState.Pitching: { diff --git a/tests/plugin-test/copy-plugin/build/main.js b/tests/plugin-test/copy-plugin/build/main.js index fc19c4b0817..428e8108b62 100644 --- a/tests/plugin-test/copy-plugin/build/main.js +++ b/tests/plugin-test/copy-plugin/build/main.js @@ -1 +1 @@ -(()=>{"use strict";var r={},t={};function e(i){var o=t[i];if(void 0!==o)return o.exports;var n=t[i]={exports:{}};return r[i](n,n.exports,e),n.exports}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(r){if("object"==typeof window)return window}}(),e.rv=function(){return"1.0.3"},(()=>{e.g.importScripts&&(r=e.g.location+"");var r,t=e.g.document;if(!r&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(r=t.currentScript.src),!r)){var i=t.getElementsByTagName("script");if(i.length){for(var o=i.length-1;o>-1&&(!r||!/^http(s?):/.test(r));)r=i[o--].src}}if(!r)throw Error("Automatic publicPath is not supported in this browser");r=r.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=r})(),e.ruid="bundler=rspack@1.0.3",e.p})(); \ No newline at end of file +(()=>{"use strict";var r={},t={};function e(i){var o=t[i];if(void 0!==o)return o.exports;var n=t[i]={exports:{}};return r[i](n,n.exports,e),n.exports}e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(r){if("object"==typeof window)return window}}(),e.rv=function(){return"1.0.4"},(()=>{e.g.importScripts&&(r=e.g.location+"");var r,t=e.g.document;if(!r&&t&&(t.currentScript&&"SCRIPT"===t.currentScript.tagName.toUpperCase()&&(r=t.currentScript.src),!r)){var i=t.getElementsByTagName("script");if(i.length){for(var o=i.length-1;o>-1&&(!r||!/^http(s?):/.test(r));)r=i[o--].src}}if(!r)throw Error("Automatic publicPath is not supported in this browser");r=r.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=r})(),e.ruid="bundler=rspack@1.0.4",e.p})(); \ No newline at end of file diff --git a/tests/plugin-test/css-extract/cases/custom-loader/expected/main.css b/tests/plugin-test/css-extract/cases/custom-loader/expected/main.css new file mode 100644 index 00000000000..cebc5c1c9f8 --- /dev/null +++ b/tests/plugin-test/css-extract/cases/custom-loader/expected/main.css @@ -0,0 +1,4 @@ +body { + background: red; +} + diff --git a/tests/plugin-test/css-extract/cases/custom-loader/index.js b/tests/plugin-test/css-extract/cases/custom-loader/index.js new file mode 100644 index 00000000000..4fe51c72d64 --- /dev/null +++ b/tests/plugin-test/css-extract/cases/custom-loader/index.js @@ -0,0 +1 @@ +import "./style.css"; diff --git a/tests/plugin-test/css-extract/cases/custom-loader/loader.js b/tests/plugin-test/css-extract/cases/custom-loader/loader.js new file mode 100644 index 00000000000..4ee22a3a9d6 --- /dev/null +++ b/tests/plugin-test/css-extract/cases/custom-loader/loader.js @@ -0,0 +1,3 @@ +module.exports = (content) => { + return content +} diff --git a/tests/plugin-test/css-extract/cases/custom-loader/style.css b/tests/plugin-test/css-extract/cases/custom-loader/style.css new file mode 100644 index 00000000000..67ce83e4d09 --- /dev/null +++ b/tests/plugin-test/css-extract/cases/custom-loader/style.css @@ -0,0 +1,3 @@ +body { + background: red; +} diff --git a/tests/plugin-test/css-extract/cases/custom-loader/webpack.config.js b/tests/plugin-test/css-extract/cases/custom-loader/webpack.config.js new file mode 100644 index 00000000000..12c20409735 --- /dev/null +++ b/tests/plugin-test/css-extract/cases/custom-loader/webpack.config.js @@ -0,0 +1,25 @@ +const { CssExtractRspackPlugin } = require("@rspack/core"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + entry: "./index.js", + module: { + rules: [ + { + test: /\.css$/, + use: [ + "./loader", + { + loader: CssExtractRspackPlugin.loader + }, + "css-loader" + ] + }, + ] + }, + plugins: [ + new CssExtractRspackPlugin({ + filename: "[name].css" + }) + ] +}; From 56a30b437f0c074655e68bfad1e6f49e2d1d5acf Mon Sep 17 00:00:00 2001 From: neverland Date: Thu, 12 Sep 2024 22:12:25 +0800 Subject: [PATCH 37/51] docs: update slogan for who is using (#7881) --- .../{BuiltWithRspack => WhoIsUsing}/assets/amazon.svg | 0 .../{BuiltWithRspack => WhoIsUsing}/assets/bit.svg | 0 .../assets/bytedance.svg | 0 .../{BuiltWithRspack => WhoIsUsing}/assets/discord.svg | 0 .../{BuiltWithRspack => WhoIsUsing}/assets/intuit.svg | 0 .../assets/microsoft.svg | 0 .../{BuiltWithRspack => WhoIsUsing}/assets/nio.svg | 0 .../{BuiltWithRspack => WhoIsUsing}/index.tsx | 10 +++++----- website/theme/components/Landingpage/index.tsx | 5 ++--- website/theme/i18n/enUS.ts | 4 ++-- website/theme/i18n/zhCN.ts | 4 ++-- 11 files changed, 11 insertions(+), 12 deletions(-) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/amazon.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/bit.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/bytedance.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/discord.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/intuit.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/microsoft.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/assets/nio.svg (100%) rename website/theme/components/Landingpage/{BuiltWithRspack => WhoIsUsing}/index.tsx (87%) diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/amazon.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/amazon.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/amazon.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/amazon.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/bit.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/bit.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/bit.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/bit.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/bytedance.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/bytedance.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/bytedance.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/bytedance.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/discord.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/discord.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/discord.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/discord.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/intuit.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/intuit.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/intuit.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/intuit.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/microsoft.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/microsoft.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/microsoft.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/microsoft.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/nio.svg similarity index 100% rename from website/theme/components/Landingpage/BuiltWithRspack/assets/nio.svg rename to website/theme/components/Landingpage/WhoIsUsing/assets/nio.svg diff --git a/website/theme/components/Landingpage/BuiltWithRspack/index.tsx b/website/theme/components/Landingpage/WhoIsUsing/index.tsx similarity index 87% rename from website/theme/components/Landingpage/BuiltWithRspack/index.tsx rename to website/theme/components/Landingpage/WhoIsUsing/index.tsx index cf53c2aab71..f6b71eddae0 100644 --- a/website/theme/components/Landingpage/BuiltWithRspack/index.tsx +++ b/website/theme/components/Landingpage/WhoIsUsing/index.tsx @@ -1,4 +1,4 @@ -import { BuiltWithRspack as BaseBuiltWithRspack } from '@rstack-dev/doc-ui/built-with-rspack'; +import { BuiltWithRspack } from '@rstack-dev/doc-ui/built-with-rspack'; import { containerStyle, innerContainerStyle, @@ -68,14 +68,14 @@ const companyList: Company[] = [ }, ]; -const BuiltWithRspack: React.FC = memo(() => { +const WhoIsUsing: React.FC = memo(() => { const t = useI18n(); return (

-
@@ -83,4 +83,4 @@ const BuiltWithRspack: React.FC = memo(() => { ); }); -export default BuiltWithRspack; +export default WhoIsUsing; diff --git a/website/theme/components/Landingpage/index.tsx b/website/theme/components/Landingpage/index.tsx index a90e6f1815a..26d40a696df 100644 --- a/website/theme/components/Landingpage/index.tsx +++ b/website/theme/components/Landingpage/index.tsx @@ -1,10 +1,9 @@ import { BackgroundImage } from '@rstack-dev/doc-ui/background-image'; -import { useEffect, useState } from 'react'; import { Benchmark } from './Benchmark'; -import BuiltWithRspack from './BuiltWithRspack'; import FullyFeatured from './FullyFeatured'; import Hero from './Hero'; import ToolStack from './ToolStack'; +import WhoIsUsing from './WhoIsUsing'; import WhyRspack from './WhyRspack'; import styles from './index.module.scss'; @@ -17,7 +16,7 @@ const LandingPage = () => { - +
); }; diff --git a/website/theme/i18n/enUS.ts b/website/theme/i18n/enUS.ts index 0c262de86b2..c8f43bb11c6 100644 --- a/website/theme/i18n/enUS.ts +++ b/website/theme/i18n/enUS.ts @@ -65,8 +65,8 @@ export const EN_US = { toolStackDesc: 'High-performance tool stack built around Rspack to boost modern web development', - // Built with Rspack - builtWithRspack: 'Built with Rspack', + // Who is using + whoIsUsing: 'Trusted By Innovators', // HomeFooter coldStart: 'Cold Start', diff --git a/website/theme/i18n/zhCN.ts b/website/theme/i18n/zhCN.ts index 98901538f7f..17d8449e658 100644 --- a/website/theme/i18n/zhCN.ts +++ b/website/theme/i18n/zhCN.ts @@ -55,8 +55,8 @@ export const ZH_CN: Record = { toolStackTitle: '工具栈', toolStackDesc: '围绕 Rspack 打造的高性能工具栈,助力现代 Web 开发', - // Built with Rspack - builtWithRspack: '谁在使用 Rspack', + // Who is using + whoIsUsing: '谁在使用 Rspack', // HomeFooter coldStart: '冷启动(dev)', From 0ef083840fbd50d2874a822b92f706206e41a3c6 Mon Sep 17 00:00:00 2001 From: Vugar Ahmadov Date: Fri, 13 Sep 2024 05:42:49 +0400 Subject: [PATCH 38/51] docs: add ABB logo at built with rspack (#7880) --- .../Landingpage/WhoIsUsing/assets/abb.svg | 45 +++++++++++++++++++ .../Landingpage/WhoIsUsing/index.tsx | 7 +++ 2 files changed, 52 insertions(+) create mode 100644 website/theme/components/Landingpage/WhoIsUsing/assets/abb.svg diff --git a/website/theme/components/Landingpage/WhoIsUsing/assets/abb.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/abb.svg new file mode 100644 index 00000000000..900852cd868 --- /dev/null +++ b/website/theme/components/Landingpage/WhoIsUsing/assets/abb.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/theme/components/Landingpage/WhoIsUsing/index.tsx b/website/theme/components/Landingpage/WhoIsUsing/index.tsx index f6b71eddae0..c7da33e0bfe 100644 --- a/website/theme/components/Landingpage/WhoIsUsing/index.tsx +++ b/website/theme/components/Landingpage/WhoIsUsing/index.tsx @@ -6,6 +6,7 @@ import { import { memo } from 'react'; import { Link } from 'rspress/theme'; import { useI18n } from '../../../i18n'; +import abbLogo from './assets/abb.svg'; import amazonLogo from './assets/amazon.svg'; import bitDevLogo from './assets/bit.svg'; import bytedanceLogo from './assets/bytedance.svg'; @@ -66,6 +67,12 @@ const companyList: Company[] = [ url: 'https://nio.com', width: 115, }, + { + name: 'ABB', + logo: abbLogo, + url: 'https://abb-bank.az/en', + width: 100, + }, ]; const WhoIsUsing: React.FC = memo(() => { From 44c7de3b6061566bd3c3ecaf0a12ea7bde87411f Mon Sep 17 00:00:00 2001 From: Burhan <7893167+ibuli@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:50:13 +0530 Subject: [PATCH 39/51] docs: add sequoia logo at who is using (#7882) doc: add sequoia logo at who is using Co-authored-by: neverland --- .../components/Landingpage/WhoIsUsing/assets/sequoia.svg | 3 +++ website/theme/components/Landingpage/WhoIsUsing/index.tsx | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 website/theme/components/Landingpage/WhoIsUsing/assets/sequoia.svg diff --git a/website/theme/components/Landingpage/WhoIsUsing/assets/sequoia.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/sequoia.svg new file mode 100644 index 00000000000..dcf0cb7f2c3 --- /dev/null +++ b/website/theme/components/Landingpage/WhoIsUsing/assets/sequoia.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/theme/components/Landingpage/WhoIsUsing/index.tsx b/website/theme/components/Landingpage/WhoIsUsing/index.tsx index c7da33e0bfe..f022acb7975 100644 --- a/website/theme/components/Landingpage/WhoIsUsing/index.tsx +++ b/website/theme/components/Landingpage/WhoIsUsing/index.tsx @@ -14,6 +14,7 @@ import discordLogo from './assets/discord.svg'; import intuitLogo from './assets/intuit.svg'; import microsoftLogo from './assets/microsoft.svg'; import nioLogo from './assets/nio.svg'; +import sequoiaLogo from './assets/sequoia.svg'; type Company = { name: string; @@ -73,6 +74,12 @@ const companyList: Company[] = [ url: 'https://abb-bank.az/en', width: 100, }, + { + name: 'Sequoia', + logo: sequoiaLogo, + url: 'https://www.sequoia.com/', + width: 150, + }, ]; const WhoIsUsing: React.FC = memo(() => { From 23aa15792f65633bb47a3fa1c22377d87de6788d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Patchane?= Date: Fri, 13 Sep 2024 04:23:31 +0200 Subject: [PATCH 40/51] docs: add getaround logo at who is using (#7883) Co-authored-by: neverland --- .../components/Landingpage/WhoIsUsing/assets/getaround.svg | 3 +++ website/theme/components/Landingpage/WhoIsUsing/index.tsx | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 website/theme/components/Landingpage/WhoIsUsing/assets/getaround.svg diff --git a/website/theme/components/Landingpage/WhoIsUsing/assets/getaround.svg b/website/theme/components/Landingpage/WhoIsUsing/assets/getaround.svg new file mode 100644 index 00000000000..cf4f2603e8b --- /dev/null +++ b/website/theme/components/Landingpage/WhoIsUsing/assets/getaround.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/theme/components/Landingpage/WhoIsUsing/index.tsx b/website/theme/components/Landingpage/WhoIsUsing/index.tsx index f022acb7975..dc55393a6fd 100644 --- a/website/theme/components/Landingpage/WhoIsUsing/index.tsx +++ b/website/theme/components/Landingpage/WhoIsUsing/index.tsx @@ -11,6 +11,7 @@ import amazonLogo from './assets/amazon.svg'; import bitDevLogo from './assets/bit.svg'; import bytedanceLogo from './assets/bytedance.svg'; import discordLogo from './assets/discord.svg'; +import getaroundLogo from './assets/getaround.svg'; import intuitLogo from './assets/intuit.svg'; import microsoftLogo from './assets/microsoft.svg'; import nioLogo from './assets/nio.svg'; @@ -80,6 +81,12 @@ const companyList: Company[] = [ url: 'https://www.sequoia.com/', width: 150, }, + { + name: 'Getaround', + logo: getaroundLogo, + url: 'https://getaround.com', + width: 110, + }, ]; const WhoIsUsing: React.FC = memo(() => { From f3eb7d5d70f95c26a806250870a13cfa4b6d1067 Mon Sep 17 00:00:00 2001 From: Soon Date: Fri, 13 Sep 2024 12:41:05 +0800 Subject: [PATCH 41/51] refactor(types): remove @ts-expect-error in loaderRunner and StatsFactory (#7864) * chore: ts * chore: remove more ts-expect-error * chore: adopt suggestions * chore: refactor getResolve type --- packages/rspack/etc/api.md | 24 +++-- packages/rspack/src/Resolver.ts | 14 ++- packages/rspack/src/config/adapterRuleUse.ts | 37 +++++--- packages/rspack/src/loader-runner/index.ts | 59 ++++++------ .../rspack/src/node/NodeEnvironmentPlugin.ts | 4 +- .../src/stats/DefaultStatsFactoryPlugin.ts | 91 +++++++++++-------- .../src/stats/DefaultStatsPrinterPlugin.ts | 15 ++- packages/rspack/src/stats/StatsFactory.ts | 12 +-- .../rspack/src/stats/statsFactoryUtils.ts | 29 ++++-- packages/rspack/src/util/comparators.ts | 5 +- packages/rspack/src/util/smartGrouping.ts | 10 +- 11 files changed, 169 insertions(+), 131 deletions(-) diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index ef64eb52be0..054e7b1cce6 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -3295,7 +3295,7 @@ export class EnvironmentPlugin { } // @public (undocumented) -type ErrorWithDetail = Error & { +type ErrorWithDetails = Error & { details?: string; }; @@ -4511,10 +4511,10 @@ interface GlobalPassOption { type GotHandler = (result: any | null, callback: (error: Error | null) => void) => void; // @public (undocumented) -type GroupConfig = { +type GroupConfig = { getKeys: (arg0: any) => string[] | undefined; - createGroup: (arg0: string, arg1: (T | R)[], arg2: T[]) => R; - getOptions?: ((arg0: string, arg1: T[]) => GroupOptions) | undefined; + createGroup: (key: string, arg1: (T | R)[], arg2: T[]) => R; + getOptions?: ((key: string, arg1: T[]) => GroupOptions) | undefined; }; // @public @@ -4577,7 +4577,7 @@ type Hooks = Readonly<{ extract: HookMap>; filter: HookMap>; filterSorted: HookMap>; - groupResults: HookMap>; + groupResults: HookMap[], StatsFactoryContext], undefined>>; filterResults: HookMap>; sort: HookMap number)[], @@ -5892,11 +5892,11 @@ export interface LoaderContext { // (undocumented) addMissingDependency(missing: string): void; // (undocumented) - async(): (err?: Error | null, content?: string | Buffer, sourceMap?: string | SourceMap, additionalData?: AdditionalData) => void; + async(): LoaderContextCallback; // (undocumented) cacheable(cacheable?: boolean): void; // (undocumented) - callback(err?: Error | null, content?: string | Buffer, sourceMap?: string | SourceMap, additionalData?: AdditionalData): void; + callback: LoaderContextCallback; // (undocumented) clearDependencies(): void; // (undocumented) @@ -5930,7 +5930,7 @@ export interface LoaderContext { // (undocumented) getOptions(schema?: any): OptionsType; // (undocumented) - getResolve(options: Resolve): (context: any, request: any, callback: any) => Promise; + getResolve(options: Resolve): ((context: string, request: string, callback: ResolveCallback) => void) | ((context: string, request: string) => Promise); // (undocumented) hot?: boolean; // (undocumented) @@ -5980,6 +5980,9 @@ export interface LoaderContext { version: 2; } +// @public (undocumented) +type LoaderContextCallback = (err?: Error | null, content?: string | Buffer, sourceMap?: string | SourceMap, additionalData?: AdditionalData) => void; + // @public (undocumented) export type LoaderDefinition = LoaderDefinitionFunction & { raw?: false; @@ -9821,6 +9824,9 @@ export type ResolveAlias = z.infer; // @public (undocumented) const resolveAlias: z.ZodRecord, z.ZodString]>, z.ZodArray]>, "many">]>>; +// @public (undocumented) +type ResolveCallback = (err: null | ErrorWithDetails, res?: string | false, req?: ResolveRequest) => void; + // @public (undocumented) type ResolveContext = {}; @@ -9861,7 +9867,7 @@ class Resolver { // (undocumented) binding: binding.JsResolver; // (undocumented) - resolve(context: object, path: string, request: string, resolveContext: ResolveContext, callback: (err: null | ErrorWithDetail, res?: string | false) => void): void; + resolve(context: object, path: string, request: string, resolveContext: ResolveContext, callback: ResolveCallback): void; // (undocumented) resolveSync(context: object, path: string, request: string): string | false; // (undocumented) diff --git a/packages/rspack/src/Resolver.ts b/packages/rspack/src/Resolver.ts index eb527eff5aa..33465cac426 100644 --- a/packages/rspack/src/Resolver.ts +++ b/packages/rspack/src/Resolver.ts @@ -1,10 +1,12 @@ import type * as binding from "@rspack/binding"; import { type Resolve, getRawResolve } from "./config"; +import type { + ErrorWithDetails, + ResolveCallback +} from "./config/adapterRuleUse"; type ResolveContext = {}; -type ErrorWithDetail = Error & { details?: string }; - type ResolveOptionsWithDependencyType = Resolve & { dependencyCategory?: string; resolveToContext?: boolean; @@ -30,17 +32,13 @@ export class Resolver { path: string, request: string, resolveContext: ResolveContext, - callback: ( - err: null | ErrorWithDetail, - res?: string | false - // req?: ResolveRequest - ) => void + callback: ResolveCallback ): void { try { const res = this.binding.resolveSync(path, request); callback(null, res); } catch (err) { - callback(err as ErrorWithDetail); + callback(err as ErrorWithDetails); } } diff --git a/packages/rspack/src/config/adapterRuleUse.ts b/packages/rspack/src/config/adapterRuleUse.ts index b069c79b41a..25313f0dba1 100644 --- a/packages/rspack/src/config/adapterRuleUse.ts +++ b/packages/rspack/src/config/adapterRuleUse.ts @@ -50,24 +50,30 @@ export interface AdditionalData { [index: string]: any; } +export type LoaderContextCallback = ( + err?: Error | null, + content?: string | Buffer, + sourceMap?: string | SourceMap, + additionalData?: AdditionalData +) => void; + +export type ErrorWithDetails = Error & { details?: string }; + +// aligned with https://github.com/webpack/webpack/blob/64e8e33151c3fabd3f1917851193e458a526e803/declarations/LoaderContext.d.ts#L19 +export type ResolveCallback = ( + err: null | ErrorWithDetails, + res?: string | false, + req?: ResolveRequest +) => void; + export interface LoaderContext { version: 2; resource: string; resourcePath: string; resourceQuery: string; resourceFragment: string; - async(): ( - err?: Error | null, - content?: string | Buffer, - sourceMap?: string | SourceMap, - additionalData?: AdditionalData - ) => void; - callback( - err?: Error | null, - content?: string | Buffer, - sourceMap?: string | SourceMap, - additionalData?: AdditionalData - ): void; + async(): LoaderContextCallback; + callback: LoaderContextCallback; cacheable(cacheable?: boolean): void; sourceMap: boolean; rootContext: string; @@ -114,7 +120,12 @@ export interface LoaderContext { ): void; getResolve( options: Resolve - ): (context: any, request: any, callback: any) => Promise; + ): + | ((context: string, request: string, callback: ResolveCallback) => void) + | (( + context: string, + request: string + ) => Promise); getLogger(name: string): Logger; emitError(error: Error): void; emitWarning(warning: Error): void; diff --git a/packages/rspack/src/loader-runner/index.ts b/packages/rspack/src/loader-runner/index.ts index 0089f2beea1..a4b375c2eb9 100644 --- a/packages/rspack/src/loader-runner/index.ts +++ b/packages/rspack/src/loader-runner/index.ts @@ -33,6 +33,7 @@ import { NonErrorEmittedError, type RspackError } from "../RspackError"; import { BUILTIN_LOADER_PREFIX, type LoaderContext, + type LoaderContextCallback, isUseSimpleSourceMap, isUseSourceMap } from "../config/adapterRuleUse"; @@ -236,22 +237,21 @@ const runSyncOrAsync = promisify(function runSyncOrAsync( fn: Function, context: LoaderContext, args: any[], - callback: (err: Error | null, args: any[]) => void + callback: (err: Error | null | undefined, args: any[]) => void ) { let isSync = true; let isDone = false; let isError = false; // internal error let reportedError = false; - // @ts-expect-error loader-runner leverages `arguments` to achieve the same functionality. context.async = function async() { if (isDone) { - if (reportedError) return; // ignore + if (reportedError) return undefined as any; // ignore throw new Error("async(): The callback was already called."); } isSync = false; return innerCallback; }; - const innerCallback = (context.callback = (err, ...args) => { + const innerCallback: LoaderContextCallback = (err, ...args) => { if (isDone) { if (reportedError) return; // ignore throw new Error("callback(): The callback was already called."); @@ -259,13 +259,14 @@ const runSyncOrAsync = promisify(function runSyncOrAsync( isDone = true; isSync = false; try { - // @ts-expect-error callback(err, args); } catch (e) { isError = true; throw e; } - }); + }; + context.callback = innerCallback; + try { const result = (function LOADER_EXECUTION() { return fn.apply(context, args); @@ -273,8 +274,7 @@ const runSyncOrAsync = promisify(function runSyncOrAsync( if (isSync) { isDone = true; if (result === undefined) { - // @ts-expect-error - callback(); + callback(null, []); return; } if ( @@ -306,8 +306,7 @@ const runSyncOrAsync = promisify(function runSyncOrAsync( } isDone = true; reportedError = true; - // @ts-expect-error - callback(e); + callback(e as Error, []); } }); @@ -585,26 +584,27 @@ export async function runLoaders( loaderContext.resolve = function resolve(context, request, callback) { resolver.resolve({}, context, request, getResolveContext(), callback); }; - // @ts-expect-error TODO + loaderContext.getResolve = function getResolve(options) { const child = options ? resolver.withOptions(options as any) : resolver; return (context, request, callback) => { if (callback) { child.resolve({}, context, request, getResolveContext(), callback); - } else { - return new Promise((resolve, reject) => { - child.resolve( - {}, - context, - request, - getResolveContext(), - (err, result) => { - if (err) reject(err); - else resolve(result); - } - ); - }); + return; } + // TODO: (type) our native resolver return value is "string | false" but webpack type is "string" + return new Promise((resolve, reject) => { + child.resolve( + {}, + context, + request, + getResolveContext(), + (err, result) => { + if (err) reject(err); + else resolve(result); + } + ); + }); }; }; loaderContext.getLogger = function getLogger(name) { @@ -651,7 +651,7 @@ export async function runLoaders( sourceMap?, assetInfo? ) { - let source: Source; + let source: Source | undefined = undefined; if (sourceMap) { if ( typeof sourceMap === "string" && @@ -679,13 +679,10 @@ export async function runLoaders( content ); } - // @ts-expect-error - compiler._lastCompilation.__internal__emit_asset_from_loader( + compiler._lastCompilation!.__internal__emit_asset_from_loader( name, - // @ts-expect-error - source, - // @ts-expect-error - assetInfo, + source!, + assetInfo!, context._moduleIdentifier ); }; diff --git a/packages/rspack/src/node/NodeEnvironmentPlugin.ts b/packages/rspack/src/node/NodeEnvironmentPlugin.ts index fb77c23607a..69a386d22ac 100644 --- a/packages/rspack/src/node/NodeEnvironmentPlugin.ts +++ b/packages/rspack/src/node/NodeEnvironmentPlugin.ts @@ -7,8 +7,8 @@ * Copyright (c) JS Foundation and other contributors * https://github.com/webpack/webpack/blob/main/LICENSE */ -// @ts-expect-error -import CachedInputFileSystem from "enhanced-resolve/lib/CachedInputFileSystem"; +// @ts-expect-error we directly import from enhanced-resolve inner js file to improve performance +import CachedInputFileSystem from "enhanced-resolve/lib/CachedInputFileSystem.js"; import fs from "graceful-fs"; import type { Compiler } from ".."; diff --git a/packages/rspack/src/stats/DefaultStatsFactoryPlugin.ts b/packages/rspack/src/stats/DefaultStatsFactoryPlugin.ts index 01576fc7807..ebdc4332e72 100644 --- a/packages/rspack/src/stats/DefaultStatsFactoryPlugin.ts +++ b/packages/rspack/src/stats/DefaultStatsFactoryPlugin.ts @@ -50,6 +50,7 @@ import type { StatsChunkOrigin, StatsError, StatsModuleReason, + StatsModuleTraceItem, StatsProfile } from "./statsFactoryUtils"; import { @@ -105,12 +106,13 @@ const MERGER: Record< const ASSETS_GROUPERS: Record< string, ( - groupConfigs: GroupConfig[], + // use any type aligned with https://github.com/webpack/webpack/blob/4b4ca3bb53f36a5b8fc6bc1bd976ed7af161bd80/lib/stats/StatsFactory.js#L18 + groupConfigs: GroupConfig[], context: KnownStatsFactoryContext, options: NormalizedStatsOptions ) => void > = { - _: (groupConfigs, context, options) => { + _: (groupConfigs, _context, options) => { const groupByFlag = (name: keyof KnownStatsAsset, exclude?: boolean) => { groupConfigs.push({ getKeys: (asset: KnownStatsAsset) => { @@ -122,7 +124,6 @@ const ASSETS_GROUPERS: Record< force: exclude }; }, - // @ts-expect-error createGroup: ( key: string, children: KnownStatsAsset[], @@ -184,8 +185,7 @@ const ASSETS_GROUPERS: Record< } return keys; }, - // @ts-expect-error - createGroup: (key, children: KnownStatsAsset[]) => { + createGroup: (key: string, children: KnownStatsAsset[]) => { return { type: groupAssetsByPath ? "assets by path" : "assets by extension", name: key, @@ -202,8 +202,7 @@ const ASSETS_GROUPERS: Record< getKeys: asset => { return asset.info?.[name] ? ["1"] : undefined; }, - // @ts-expect-error - createGroup: (key, children: KnownStatsAsset[]) => { + createGroup: (key: string, children: KnownStatsAsset[]) => { return { type: "assets by info", info: { @@ -233,8 +232,7 @@ const ASSETS_GROUPERS: Record< getKeys: asset => { return asset[name]; }, - // @ts-expect-error - createGroup: (key, children: KnownStatsAsset[]) => { + createGroup: (key: string, children: KnownStatsAsset[]) => { return { type: "assets by chunk", [name]: [key], @@ -249,7 +247,7 @@ const ASSETS_GROUPERS: Record< groupByNames("chunkIdHints"); groupByNames("auxiliaryChunkIdHints"); }, - excludeAssets: (groupConfigs, context, { excludeAssets }) => { + excludeAssets: (groupConfigs, _context, { excludeAssets }) => { groupConfigs.push({ getKeys: asset => { const ident = asset.name; @@ -260,8 +258,11 @@ const ASSETS_GROUPERS: Record< groupChildren: false, force: true }), - // @ts-expect-error - createGroup: (key, children: KnownStatsAsset[], assets) => ({ + createGroup: ( + _key: string, + children: KnownStatsAsset[], + assets: KnownStatsAsset[] + ) => ({ type: "hidden assets", filteredChildren: assets.length, ...assetGroup(children) @@ -275,12 +276,12 @@ const MODULES_GROUPERS = ( ): Record< string, ( - groupConfigs: GroupConfig[], + groupConfigs: GroupConfig[], context: KnownStatsFactoryContext, options: NormalizedStatsOptions ) => void > => ({ - _: (groupConfigs, context, options) => { + _: (groupConfigs, _context, options) => { const groupByFlag = (name: string, type: unknown, exclude?: boolean) => { groupConfigs.push({ getKeys: module => { @@ -292,9 +293,8 @@ const MODULES_GROUPERS = ( force: exclude }; }, - // @ts-expect-error createGroup: ( - key, + key: string, children: KnownStatsModule[], modules: KnownStatsModule[] ) => { @@ -353,7 +353,6 @@ const MODULES_GROUPERS = ( force: exclude }; }, - // @ts-expect-error createGroup: (key, children: KnownStatsModule[], modules) => { const exclude = key === "runtime" && !options.runtimeModules; return { @@ -396,8 +395,11 @@ const MODULES_GROUPERS = ( } return keys; }, - // @ts-expect-error - createGroup: (key, children: KnownStatsModule[], modules) => { + createGroup: ( + key: string, + children: KnownStatsModule[], + _modules: KnownStatsModule[] + ) => { const isDataUrl = key.startsWith("data:"); return { type: isDataUrl @@ -413,7 +415,7 @@ const MODULES_GROUPERS = ( }); } }, - excludeModules: (groupConfigs, context, { excludeModules }) => { + excludeModules: (groupConfigs, _context, { excludeModules }) => { groupConfigs.push({ getKeys: module => { const name = module.name; @@ -426,8 +428,12 @@ const MODULES_GROUPERS = ( groupChildren: false, force: true }), - // @ts-expect-error - createGroup: (key, children: KnownStatsModule[], modules) => ({ + + createGroup: ( + _key: string, + children: KnownStatsModule[], + _modules: KnownStatsModule[] + ) => ({ type: "hidden modules", filteredChildren: children.length, ...moduleGroup(children) @@ -441,7 +447,7 @@ const RESULT_GROUPERS: Record< Record< string, ( - groupConfigs: GroupConfig[], + groupConfigs: GroupConfig[], context: KnownStatsFactoryContext, options: NormalizedStatsOptions ) => void @@ -480,22 +486,22 @@ const RESULT_SORTERS: Record< > > = { "compilation.chunks": { - chunksSort: (comparators, context, { chunksSort }) => { + chunksSort: (comparators, _context, { chunksSort }) => { comparators.push(sortByField(chunksSort)); } }, "compilation.modules": { - modulesSort: (comparators, context, { modulesSort }) => { + modulesSort: (comparators, _context, { modulesSort }) => { comparators.push(sortByField(modulesSort)); } }, "chunk.modules": { - chunkModulesSort: (comparators, context, { chunkModulesSort }) => { + chunkModulesSort: (comparators, _context, { chunkModulesSort }) => { comparators.push(sortByField(chunkModulesSort)); } }, "module.modules": { - nestedModulesSort: (comparators, context, { nestedModulesSort }) => { + nestedModulesSort: (comparators, _context, { nestedModulesSort }) => { comparators.push(sortByField(nestedModulesSort)); } }, @@ -631,7 +637,7 @@ const EXTRACT_ERROR: Record< `${type}.moduleTrace`, error.moduleTrace, context - ); + ) as StatsModuleTraceItem[]; }, errorDetails: (object, error) => { object.details = error.details; @@ -851,7 +857,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { const assetsByChunkName = statsCompilation.assetsByChunkName!; const assetMap: Map = new Map(); - const assets = new Set(); + const assets: Set = new Set(); for (const asset of compilationAssets) { const item: PreprocessedAsset = { @@ -914,7 +920,11 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { const { type, getStatsCompilation } = context; const statsCompilation = getStatsCompilation(compilation); const chunks = statsCompilation.chunks; - object.chunks = factory.create(`${type}.chunks`, chunks, context); + object.chunks = factory.create( + `${type}.chunks`, + chunks, + context + ) as StatsChunk[]; }, modules: ( object, @@ -1121,7 +1131,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { object.auxiliaryChunkIdHints = asset.auxiliaryChunkIdHints.filter(Boolean); }, - relatedAssets: (object, asset, context, options, factory) => { + relatedAssets: (object, asset, context, _options, factory) => { const { type } = context; object.related = factory.create( `${type.slice(0, -8)}.related`, @@ -1201,7 +1211,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { } }, module$visible: { - _: (object, module, context, options, factory) => { + _: (object, module, context, _options, factory) => { const { type } = context; const { commonAttributes } = module; if (commonAttributes.moduleDescriptor) { @@ -1320,7 +1330,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { } }, moduleIssuer: { - _: (object, module, context, options, factory) => { + _: (object, module, _context, _options, _factory) => { if (module.moduleDescriptor) { object.identifier = module.moduleDescriptor.identifier; object.name = module.moduleDescriptor.name; @@ -1395,7 +1405,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { object.modules = limited.children; object.filteredModules = limited.filteredChildren; }, - chunkOrigins: (object, chunk, context, options, factory) => { + chunkOrigins: (object, chunk, context, _options, factory) => { const { type } = context; object.origins = factory.create( `${type}.origins`, @@ -1405,7 +1415,7 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { } }, chunkOrigin: { - _: (object, origin, context) => { + _: (object, origin, _context) => { const { moduleDescriptor, loc, request } = origin; const statsChunkOrigin = { module: moduleDescriptor ? moduleDescriptor.identifier : "", @@ -1423,7 +1433,13 @@ const SIMPLE_EXTRACTORS: SimpleExtractors = { error: EXTRACT_ERROR, warning: EXTRACT_ERROR, moduleTraceItem: { - _: (object, { origin, module }, context, { requestShortener }, factory) => { + _: ( + object, + { origin, module }, + _context, + { requestShortener }, + _factory + ) => { if (origin.moduleDescriptor) { object.originIdentifier = origin.moduleDescriptor.identifier; object.originName = origin.moduleDescriptor.name; @@ -1481,8 +1497,7 @@ export class DefaultStatsFactoryPlugin { compiler.hooks.compilation.tap("DefaultStatsFactoryPlugin", compilation => { compilation.hooks.statsFactory.tap( "DefaultStatsFactoryPlugin", - // @ts-expect-error - (stats: StatsFactory, options: NormalizedStatsOptions, context) => { + (stats: StatsFactory, options: StatsOptions) => { iterateConfig(SIMPLE_EXTRACTORS, options, (hookFor, fn) => { stats.hooks.extract .for(hookFor) diff --git a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts index 84cb9d62974..ca7ab9f88a6 100644 --- a/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts +++ b/packages/rspack/src/stats/DefaultStatsPrinterPlugin.ts @@ -1367,8 +1367,8 @@ export class DefaultStatsPrinterPlugin { ) { start = options.colors[color]; } else { - // @ts-expect-error - start = AVAILABLE_COLORS[color]; + start = + AVAILABLE_COLORS[color as keyof typeof AVAILABLE_COLORS]; } } if (start) { @@ -1398,9 +1398,14 @@ export class DefaultStatsPrinterPlugin { for (const key of Object.keys(SIMPLE_PRINTERS)) { stats.hooks.print .for(key) - .tap("DefaultStatsPrinterPlugin", (obj, ctx) => - // @ts-expect-error - SIMPLE_PRINTERS[key](obj, ctx, stats) + .tap( + "DefaultStatsPrinterPlugin", + (obj, ctx) => + SIMPLE_PRINTERS[key]( + obj, + ctx as Required, + stats + ) as string ); } diff --git a/packages/rspack/src/stats/StatsFactory.ts b/packages/rspack/src/stats/StatsFactory.ts index d2afed54461..d3f51d6a60b 100644 --- a/packages/rspack/src/stats/StatsFactory.ts +++ b/packages/rspack/src/stats/StatsFactory.ts @@ -45,7 +45,7 @@ type Hooks = Readonly<{ SyncBailHook<[any, StatsFactoryContext, number, number], undefined> >; groupResults: HookMap< - SyncBailHook<[GroupConfig[], StatsFactoryContext], undefined> + SyncBailHook<[GroupConfig[], StatsFactoryContext], undefined> >; filterResults: HookMap< SyncBailHook<[any, StatsFactoryContext, number, number], undefined> @@ -121,10 +121,10 @@ export class StatsFactory { ), groupResults: new HookMap( () => - new SyncBailHook<[GroupConfig[], StatsFactoryContext], undefined>([ - "groupConfigs", - "context" - ]) + new SyncBailHook< + [GroupConfig[], StatsFactoryContext], + undefined + >(["groupConfigs", "context"]) ), sortResults: new HookMap( () => @@ -351,7 +351,7 @@ export class StatsFactory { } // group result items - const groupConfigs: GroupConfig[] = []; + const groupConfigs: GroupConfig[] = []; this._forEachLevel( this.hooks.groupResults, this._caches.groupResults, diff --git a/packages/rspack/src/stats/statsFactoryUtils.ts b/packages/rspack/src/stats/statsFactoryUtils.ts index 212f2198724..d094597e866 100644 --- a/packages/rspack/src/stats/statsFactoryUtils.ts +++ b/packages/rspack/src/stats/statsFactoryUtils.ts @@ -1,7 +1,8 @@ import type * as binding from "@rspack/binding"; import type { JsOriginRecord } from "@rspack/binding"; -import type { Compilation, NormalizedStatsOptions } from "../Compilation"; +import type { Compilation } from "../Compilation"; +import type { StatsOptions } from "../config"; import { type Comparator, compareIds, @@ -338,7 +339,7 @@ export const uniqueOrderedArray = ( export const iterateConfig = ( config: Record>, - options: NormalizedStatsOptions, + options: StatsOptions, fn: (a1: string, a2: Function) => void ) => { for (const hookFor of Object.keys(config)) { @@ -346,9 +347,17 @@ export const iterateConfig = ( for (const option of Object.keys(subConfig)) { if (option !== "_") { if (option.startsWith("!")) { - if (options[option.slice(1)]) continue; + if ( + // string cannot be used as key, so use "as" + (options as Record)[ + option.slice(1) + ] + ) + continue; } else { - const value = options[option]; + const value = ( + options as Record + )[option]; if ( value === false || value === undefined || @@ -437,7 +446,7 @@ export const spaceLimited = ( let children: any[] | undefined = undefined; let filteredChildren: number | undefined = undefined; // This are the groups, which take 1+ lines each - const groups = []; + const groups: ItemChildren = []; // The sizes of the groups are stored in groupSizes const groupSizes = []; // This are the items, which take 1 line each @@ -495,14 +504,13 @@ export const spaceLimited = ( } for (let i = 0; i < groups.length; i++) { if (groupSizes[i] === maxGroupSize) { - // @ts-expect-error const group = groups[i]; // run this algorithm recursively and limit the size of the children to // current size - oversize / number of groups // So it should always end up being smaller const headerSize = group.filteredChildren ? 2 : 1; const limited = spaceLimited( - group.children, + group.children!, maxGroupSize - // we should use ceil to always feet in max Math.ceil(oversize / groups.length) - @@ -576,7 +584,7 @@ export const sortByField = ( field: string ): ((a1: Object, a2: Object) => number) => { if (!field) { - const noSort = (a: any, b: any) => 0; + const noSort = (_a: any, _b: any) => 0; return noSort; } @@ -584,7 +592,6 @@ export const sortByField = ( let sortFn = compareSelect( (m: Record) => m[fieldKey], - // @ts-expect-error compareIds ); @@ -609,7 +616,9 @@ export const assetGroup = (children: StatsAsset[]) => { }; }; -export const moduleGroup = (children: KnownStatsModule[]) => { +export const moduleGroup = ( + children: { size: number; sizes: Record }[] +): { size: number; sizes: Record } => { let size = 0; const sizes: Record = {}; for (const module of children) { diff --git a/packages/rspack/src/util/comparators.ts b/packages/rspack/src/util/comparators.ts index f4a97e2cc0f..8e3780c8c11 100644 --- a/packages/rspack/src/util/comparators.ts +++ b/packages/rspack/src/util/comparators.ts @@ -70,10 +70,7 @@ export const concatComparators = (...comps: Array): Comparator => { return result; }; -export const compareIds = ( - a: string | number, - b: string | number -): -1 | 0 | 1 => { +export const compareIds = (a: T, b: T): -1 | 0 | 1 => { if (typeof a !== typeof b) { return typeof a < typeof b ? -1 : 1; } diff --git a/packages/rspack/src/util/smartGrouping.ts b/packages/rspack/src/util/smartGrouping.ts index 046270233e1..73d3f0fc48c 100644 --- a/packages/rspack/src/util/smartGrouping.ts +++ b/packages/rspack/src/util/smartGrouping.ts @@ -14,14 +14,14 @@ type GroupOptions = { targetGroupCount?: number | undefined; }; -export type GroupConfig = { +export type GroupConfig = { getKeys: (arg0: any) => string[] | undefined; - createGroup: (arg0: string, arg1: (T | R)[], arg2: T[]) => R; - getOptions?: ((arg0: string, arg1: T[]) => GroupOptions) | undefined; + createGroup: (key: string, arg1: (T | R)[], arg2: T[]) => R; + getOptions?: ((key: string, arg1: T[]) => GroupOptions) | undefined; }; type Group = { - config: GroupConfig; + config: GroupConfig; name: string; alreadyGrouped: boolean; items: Set> | undefined; @@ -34,7 +34,7 @@ type ItemWithGroups = { export const smartGrouping = ( items: T[], - groupConfigs: GroupConfig[] + groupConfigs: GroupConfig[] ): (T | R)[] => { const itemsWithGroups: Set> = new Set(); const allGroups: Map> = new Map(); From 1e25adeebd7c23a988b1fd595c2f506435abc89e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:49:23 +0800 Subject: [PATCH 42/51] release:1.0.5 (#7885) Co-authored-by: github-actions[bot] --- crates/node_binding/package.json | 2 +- npm/darwin-arm64/package.json | 2 +- npm/darwin-x64/package.json | 2 +- npm/linux-x64-gnu/package.json | 2 +- npm/win32-x64-msvc/package.json | 2 +- package.json | 2 +- packages/create-rspack/package.json | 2 +- packages/rspack-cli/package.json | 2 +- packages/rspack-dev-server/package.json | 14 +++++++------- packages/rspack-test-tools/package.json | 4 ++-- packages/rspack/package.json | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/node_binding/package.json b/crates/node_binding/package.json index 14137d0c1ce..056570be9ef 100644 --- a/crates/node_binding/package.json +++ b/crates/node_binding/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Node binding for rspack", "main": "binding.js", diff --git a/npm/darwin-arm64/package.json b/npm/darwin-arm64/package.json index e4257c870a8..ae62e27b8ac 100644 --- a/npm/darwin-arm64/package.json +++ b/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-darwin-arm64", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.darwin-arm64.node", diff --git a/npm/darwin-x64/package.json b/npm/darwin-x64/package.json index 95defe95b1c..b71e3b1edc1 100644 --- a/npm/darwin-x64/package.json +++ b/npm/darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-darwin-x64", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.darwin-x64.node", diff --git a/npm/linux-x64-gnu/package.json b/npm/linux-x64-gnu/package.json index 5c8653efbcf..1712e90cd65 100644 --- a/npm/linux-x64-gnu/package.json +++ b/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-linux-x64-gnu", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.linux-x64-gnu.node", diff --git a/npm/win32-x64-msvc/package.json b/npm/win32-x64-msvc/package.json index b30f6cff6c8..8617ed7ad1a 100644 --- a/npm/win32-x64-msvc/package.json +++ b/npm/win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/binding-win32-x64-msvc", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Node binding for rspack", "main": "rspack.win32-x64-msvc.node", diff --git a/package.json b/package.json index 475113f144c..e8029547fab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "monorepo", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "The fast Rust-based web bundler with webpack-compatible API", "private": true, diff --git a/packages/create-rspack/package.json b/packages/create-rspack/package.json index 59e0c3a34bf..dbf17a08a97 100644 --- a/packages/create-rspack/package.json +++ b/packages/create-rspack/package.json @@ -1,6 +1,6 @@ { "name": "create-rspack", - "version": "1.0.4", + "version": "1.0.5", "homepage": "https://rspack.dev", "bugs": "https://github.com/web-infra-dev/rspack/issues", "repository": { diff --git a/packages/rspack-cli/package.json b/packages/rspack-cli/package.json index c337958276e..fd82cd6ad8f 100644 --- a/packages/rspack-cli/package.json +++ b/packages/rspack-cli/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/cli", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "CLI for rspack", "publishConfig": { diff --git a/packages/rspack-dev-server/package.json b/packages/rspack-dev-server/package.json index 7bde2da2873..e448de7ecb7 100644 --- a/packages/rspack-dev-server/package.json +++ b/packages/rspack-dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/dev-server", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Development server for rspack", "main": "./dist/index.js", @@ -37,6 +37,7 @@ "directory": "packages/rspack-dev-server" }, "devDependencies": { + "@jest/test-sequencer": "^29.7.0", "@rspack/core": "workspace:*", "@rspack/dev-server": "workspace:*", "@rspack/plugin-react-refresh": "1.0.0", @@ -44,20 +45,19 @@ "@types/express": "4.17.21", "@types/mime-types": "2.1.4", "@types/ws": "8.5.10", + "css-loader": "^6.11.0", "graceful-fs": "4.2.10", "http-proxy": "^1.18.1", "jest-serializer-path": "^0.1.15", + "prettier": "3.2.5", "puppeteer": "^23.2.2", + "require-from-string": "^2.0.2", "sockjs-client": "^1.6.1", + "style-loader": "^3.3.3", "supertest": "^6.1.3", "tcp-port-used": "^1.0.2", "typescript": "5.0.2", - "style-loader": "^3.3.3", - "css-loader": "^6.11.0", - "require-from-string": "^2.0.2", - "wait-for-expect": "^3.0.2", - "prettier": "3.2.5", - "@jest/test-sequencer": "^29.7.0" + "wait-for-expect": "^3.0.2" }, "dependencies": { "chokidar": "^3.6.0", diff --git a/packages/rspack-test-tools/package.json b/packages/rspack-test-tools/package.json index 7f853cdc25d..9eec08f187a 100644 --- a/packages/rspack-test-tools/package.json +++ b/packages/rspack-test-tools/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/test-tools", - "version": "1.0.4", + "version": "1.0.5", "license": "MIT", "description": "Test tools for rspack", "main": "dist/index.js", @@ -73,8 +73,8 @@ "@monaco-editor/react": "^4.6.0", "@rspack/cli": "workspace:*", "@rspack/core": "workspace:*", - "@rspack/plugin-react-refresh": "1.0.0", "@rspack/plugin-preact-refresh": "1.0.0", + "@rspack/plugin-react-refresh": "1.0.0", "@swc/helpers": "0.5.8", "@swc/plugin-remove-console": "^2.0.8", "@types/babel__generator": "7.6.8", diff --git a/packages/rspack/package.json b/packages/rspack/package.json index cf5dfba2688..ff8a35bf598 100644 --- a/packages/rspack/package.json +++ b/packages/rspack/package.json @@ -1,6 +1,6 @@ { "name": "@rspack/core", - "version": "1.0.4", + "version": "1.0.5", "webpackVersion": "5.75.0", "license": "MIT", "description": "The fast Rust-based web bundler with webpack-compatible API", From d7a15d836bff645b552b6709053f54e082da263b Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Fri, 13 Sep 2024 15:32:49 +0800 Subject: [PATCH 43/51] refactor: clean dev server code (#7794) --- packages/rspack-dev-server/src/alias.ts | 36 +++ packages/rspack-dev-server/src/server.ts | 373 ++--------------------- 2 files changed, 57 insertions(+), 352 deletions(-) create mode 100644 packages/rspack-dev-server/src/alias.ts diff --git a/packages/rspack-dev-server/src/alias.ts b/packages/rspack-dev-server/src/alias.ts new file mode 100644 index 00000000000..d5debd2f9a5 --- /dev/null +++ b/packages/rspack-dev-server/src/alias.ts @@ -0,0 +1,36 @@ +const RESOLVER_MAP: Record = {}; +export const addResolveAlias = ( + name: string, + aliasMap: Record +) => { + const modulePath = require.resolve(name); + if (RESOLVER_MAP[modulePath]) { + throw new Error(`Should not add resolve alias to ${name} again.`); + } + const m = require.cache[modulePath]; + if (!m) { + throw new Error("Failed to resolve webpack-dev-server."); + } + RESOLVER_MAP[modulePath] = m.require.resolve; + m.require.resolve = ((id: string, options?: any) => + aliasMap[id] || + RESOLVER_MAP[modulePath]!.apply(m.require, [ + id, + options + ])) as typeof require.resolve; +}; + +export const removeResolveAlias = (name: string) => { + const modulePath = require.resolve(name); + if (!RESOLVER_MAP[modulePath]) { + return; + } + const m = require.cache[modulePath]; + if (!m) { + throw new Error("Failed to resolve webpack-dev-server"); + } + if (RESOLVER_MAP[modulePath]) { + m.require.resolve = RESOLVER_MAP[modulePath]!; + delete RESOLVER_MAP[modulePath]; + } +}; diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 000e95b80d8..610145b8562 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -13,23 +13,16 @@ import type { Server } from "node:http"; import type { Socket } from "node:net"; import { type Compiler, MultiCompiler } from "@rspack/core"; import type { FSWatcher } from "chokidar"; -import rdm from "webpack-dev-middleware"; -import WebpackDevServer, { - type OverlayMessageOptions -} from "webpack-dev-server"; +import WebpackDevServer from "webpack-dev-server"; // @ts-ignore 'package.json' is not under 'rootDir' import { version } from "../package.json"; +import { addResolveAlias, removeResolveAlias } from "./alias"; import type { DevServer, ResolvedDevServer } from "./config"; import { applyDevServerPatch } from "./patch"; applyDevServerPatch(); -const encodeOverlaySettings = (setting: OverlayMessageOptions | undefined) => - typeof setting === "function" - ? encodeURIComponent(setting.toString()) - : setting; - const getFreePort = async function getFreePort(port: string, host: string) { if (typeof port !== "undefined" && port !== null && port !== "auto") { return port; @@ -78,71 +71,10 @@ export class RspackDevServer extends WebpackDevServer { constructor(options: DevServer, compiler: Compiler | MultiCompiler) { super(options, compiler as any); + // override } - private override getClientTransport(): string { - // WARNING: we can't use `super.getClientTransport`, - // because we doesn't had same directory structure. - let clientImplementation: string | undefined; - let clientImplementationFound = true; - const isKnownWebSocketServerImplementation = - this.options.webSocketServer && - typeof this.options.webSocketServer.type === "string" && - (this.options.webSocketServer.type === "ws" || - this.options.webSocketServer.type === "sockjs"); - - let clientTransport: string | undefined; - - if (this.options.client) { - if (typeof this.options.client.webSocketTransport !== "undefined") { - clientTransport = this.options.client.webSocketTransport; - } else if (isKnownWebSocketServerImplementation) { - // @ts-expect-error: TS cannot infer webSocketServer is narrowed - clientTransport = this.options.webSocketServer.type; - } else { - clientTransport = "ws"; - } - } else { - clientTransport = "ws"; - } - - switch (typeof clientTransport) { - case "string": - // could be 'sockjs', 'ws', or a path that should be required - if (clientTransport === "sockjs") { - clientImplementation = require.resolve( - "webpack-dev-server/client/clients/SockJSClient" - ); - } else if (clientTransport === "ws") { - clientImplementation = require.resolve( - "webpack-dev-server/client/clients/WebSocketClient" - ); - } else { - try { - clientImplementation = require.resolve(clientTransport); - } catch (e) { - clientImplementationFound = false; - } - } - break; - default: - clientImplementationFound = false; - } - if (!clientImplementationFound) { - throw new Error( - `${ - !isKnownWebSocketServerImplementation - ? "When you use custom web socket implementation you must explicitly specify client.webSocketTransport. " - : "" - }client.webSocketTransport must be a string denoting a default implementation (e.g. 'sockjs', 'ws') or a full path to a JS file via require.resolve(...) which exports a class ` - ); - } - - // @ts-expect-error - return clientImplementation; - } - - override async initialize() { + async initialize() { const compilers = this.compiler instanceof MultiCompiler ? this.compiler.compilers @@ -158,22 +90,6 @@ export class RspackDevServer extends WebpackDevServer { ); } - const HMRPluginExists = compiler.options.plugins.find( - p => p?.constructor === compiler.webpack.HotModuleReplacementPlugin - ); - - if (HMRPluginExists) { - this.logger.warn( - `"hot: true" automatically applies HMR plugin, you don't have to add it manually to your webpack configuration.` - ); - } else { - // Apply the HMR plugin - const plugin = new compiler.webpack.HotModuleReplacementPlugin(); - - plugin.apply(compiler); - } - - // Apply modified version of `ansi-html-community` compiler.options.resolve.alias = { "ansi-html-community": path.resolve(__dirname, "./ansiHTML"), ...compiler.options.resolve.alias @@ -181,276 +97,29 @@ export class RspackDevServer extends WebpackDevServer { } } - if (this.options.webSocketServer) { - for (const compiler of compilers) { - this.addAdditionalEntries(compiler); - new compiler.webpack.ProvidePlugin({ - __webpack_dev_server_client__: this.getClientTransport() - }).apply(compiler); - } - } - - // @ts-expect-error: `setupHooks` is private function in base class. - this.setupHooks(); - // @ts-expect-error: `setupApp` is private function in base class. - this.setupApp(); - // @ts-expect-error: `setupHostHeaderCheck` is private function in base class. - this.setupHostHeaderCheck(); - this.setupDevMiddleware(); - // @ts-expect-error: `setupBuiltInRoutes` is private function in base class. - this.setupBuiltInRoutes(); - // @ts-expect-error: `setupWatchFiles` is private function in base class. - this.setupWatchFiles(); - // @ts-expect-error: `setupWatchStaticFiles` is private function in base class. - this.setupWatchStaticFiles(); - this.setupMiddlewares(); - // @ts-expect-error: `createServer` is private function in base class. - this.createServer(); - - if (this.options.setupExitSignals) { - const signals = ["SIGINT", "SIGTERM"]; - - let needForceShutdown = false; - - for (const signal of signals) { - const listener = () => { - if (needForceShutdown) { - process.exit(); - } - - this.logger.info( - "Gracefully shutting down. To force exit, press ^C again. Please wait..." - ); - - needForceShutdown = true; - - this.stopCallback(() => { - if (typeof this.compiler.close === "function") { - this.compiler.close(() => { - process.exit(); - }); - } else { - process.exit(); - } - }); - }; - - // @ts-expect-error: `listeners` is private function in base class. - this.listeners.push({ name: signal, listener }); - - process.on(signal, listener); - } - } - - // Proxy WebSocket without the initial http request - // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade - // @ts-expect-error: `webSocketProxies` is private function in base class. - for (const webSocketProxy of this.webSocketProxies) { - this.server.on("upgrade", webSocketProxy.upgrade); + if (typeof this.options.client === "object") { + // TODO: support progress plugin event + this.options.client.progress = false; } - } - private override setupDevMiddleware() { // @ts-expect-error - this.middleware = rdm(this.compiler, this.options.devMiddleware); - } - - private override setupMiddlewares() { - const middlewares: WebpackDevServer.Middleware[] = []; - for (const middleware of middlewares) { - if (typeof middleware === "function") { - // @ts-expect-error - this.app.use(middleware); - } else if (typeof middleware.path !== "undefined") { - // @ts-expect-error - this.app.use(middleware.path, middleware.middleware); - } else { - // @ts-expect-error - this.app.use(middleware.middleware); - } - } - - // @ts-expect-error - super.setupMiddlewares(); + await super.initialize(); } + // @ts-ignore private override addAdditionalEntries(compiler: Compiler) { - const additionalEntries = []; - // @ts-expect-error - const isWebTarget = WebpackDevServer.isWebTarget(compiler); - - // TODO maybe empty client - if (this.options.client && isWebTarget) { - let webSocketURLStr = ""; - - if (this.options.webSocketServer) { - const webSocketURL = this.options.client - .webSocketURL as WebpackDevServer.WebSocketURL; - const webSocketServer = this.options.webSocketServer; - const searchParams = new URLSearchParams(); - - let protocol: string; - - // We are proxying dev server and need to specify custom `hostname` - if (typeof webSocketURL.protocol !== "undefined") { - protocol = webSocketURL.protocol; - } else { - protocol = this.options.server.type === "http" ? "ws:" : "wss:"; - } - - searchParams.set("protocol", protocol); - - if (typeof webSocketURL.username !== "undefined") { - searchParams.set("username", webSocketURL.username); - } - - if (typeof webSocketURL.password !== "undefined") { - searchParams.set("password", webSocketURL.password); - } - - let hostname: string; - - // SockJS is not supported server mode, so `hostname` and `port` can't specified, let's ignore them - // TODO show warning about this - const isSockJSType = webSocketServer.type === "sockjs"; - - // We are proxying dev server and need to specify custom `hostname` - if (typeof webSocketURL.hostname !== "undefined") { - hostname = webSocketURL.hostname; - } - // Web socket server works on custom `hostname`, only for `ws` because `sock-js` is not support custom `hostname` - else if ( - typeof webSocketServer.options?.host !== "undefined" && - !isSockJSType - ) { - hostname = webSocketServer.options.host; - } - // The `host` option is specified - else if (typeof this.options.host !== "undefined") { - hostname = this.options.host; - } - // The `port` option is not specified - else { - hostname = "0.0.0.0"; - } - - searchParams.set("hostname", hostname); - - let port: number | string; - - // We are proxying dev server and need to specify custom `port` - if (typeof webSocketURL.port !== "undefined") { - port = webSocketURL.port; - } - // Web socket server works on custom `port`, only for `ws` because `sock-js` is not support custom `port` - else if ( - typeof webSocketServer.options?.port !== "undefined" && - !isSockJSType - ) { - port = webSocketServer.options.port; - } - // The `port` option is specified - else if (typeof this.options.port === "number") { - port = this.options.port; - } - // The `port` option is specified using `string` - else if ( - typeof this.options.port === "string" && - this.options.port !== "auto" - ) { - port = Number(this.options.port); - } - // The `port` option is not specified or set to `auto` - else { - port = "0"; - } - - searchParams.set("port", String(port)); - - let pathname = ""; - - // We are proxying dev server and need to specify custom `pathname` - if (typeof webSocketURL.pathname !== "undefined") { - pathname = webSocketURL.pathname; - } - // Web socket server works on custom `path` - else if ( - typeof webSocketServer.options?.prefix !== "undefined" || - typeof webSocketServer.options?.path !== "undefined" - ) { - pathname = - webSocketServer.options.prefix || webSocketServer.options.path; - } - - searchParams.set("pathname", pathname); - - const client = /** @type {ClientConfiguration} */ this.options.client; - - if (typeof client.logging !== "undefined") { - searchParams.set("logging", client.logging); - } - - if (typeof client.progress !== "undefined") { - searchParams.set("progress", String(client.progress)); - } - - if (typeof client.overlay !== "undefined") { - const overlayString = - typeof client.overlay === "boolean" - ? String(client.overlay) - : JSON.stringify({ - ...client.overlay, - errors: encodeOverlaySettings(client.overlay.errors), - warnings: encodeOverlaySettings(client.overlay.warnings), - runtimeErrors: encodeOverlaySettings( - client.overlay.runtimeErrors - ) - }); - - searchParams.set("overlay", overlayString); - } - - if (typeof client.reconnect !== "undefined") { - searchParams.set( - "reconnect", - typeof client.reconnect === "number" - ? String(client.reconnect) - : "10" - ); - } - - if (typeof this.options.hot !== "undefined") { - searchParams.set("hot", String(this.options.hot)); - } - - if (typeof this.options.liveReload !== "undefined") { - searchParams.set("live-reload", String(this.options.liveReload)); - } - - webSocketURLStr = searchParams.toString(); - } - - additionalEntries.push( - `${require.resolve( - "@rspack/dev-server/client/index" - )}?${webSocketURLStr}` - ); - } - - if (this.options.hot === "only") { - additionalEntries.push( - require.resolve("@rspack/core/hot/only-dev-server") - ); - } else if (this.options.hot) { - additionalEntries.push(require.resolve("@rspack/core/hot/dev-server")); - } - - const webpack = compiler.webpack; - - for (const additionalEntry of additionalEntries) { - new webpack.EntryPlugin(compiler.context, additionalEntry, { - name: undefined - }).apply(compiler); + addResolveAlias("webpack-dev-server", { + "../client/index.js": require.resolve("@rspack/dev-server/client/index"), + "webpack/hot/only-dev-server": require.resolve( + "@rspack/core/hot/only-dev-server" + ), + "webpack/hot/dev-server": require.resolve("@rspack/core/hot/dev-server") + }); + try { + // @ts-expect-error + super.addAdditionalEntries(compiler); + } finally { + removeResolveAlias("webpack-dev-server"); } } } From 3343471adba6a937b835de102db3146ee413eea6 Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Fri, 13 Sep 2024 16:15:58 +0800 Subject: [PATCH 44/51] feat(rspack_plugin_javascript): improve provide dependency (#7871) * feat(rspack_plugin_javascript): improve provide dependency * refactor: use RealDependencyLocation * chore: remove DependencyRange --- .../src/dependency/esm/provide_dependency.rs | 25 ++++++++----- .../src/parser_plugin/provide_plugin.rs | 36 ++++++++++--------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs b/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs index cd225f8dd2b..47a8fe48128 100644 --- a/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs +++ b/crates/rspack_plugin_javascript/src/dependency/esm/provide_dependency.rs @@ -1,8 +1,8 @@ use itertools::Itertools; -use rspack_core::DependencyType; use rspack_core::{ - create_exports_object_referenced, module_raw, Compilation, ExtendedReferencedExport, ModuleGraph, - NormalInitFragment, RuntimeSpec, UsedName, + create_exports_object_referenced, module_raw, Compilation, DependencyType, + ExtendedReferencedExport, ModuleGraph, NormalInitFragment, RealDependencyLocation, RuntimeSpec, + UsedName, }; use rspack_core::{AsContextDependency, Dependency, InitFragmentKey, InitFragmentStage}; use rspack_core::{DependencyCategory, DependencyId, DependencyTemplate}; @@ -12,19 +12,22 @@ use swc_core::atoms::Atom; #[derive(Debug, Clone)] pub struct ProvideDependency { - start: u32, - end: u32, id: DependencyId, request: Atom, identifier: String, ids: Vec, + range: RealDependencyLocation, } impl ProvideDependency { - pub fn new(start: u32, end: u32, request: Atom, identifier: String, ids: Vec) -> Self { + pub fn new( + range: RealDependencyLocation, + request: Atom, + identifier: String, + ids: Vec, + ) -> Self { Self { - start, - end, + range, request, identifier, ids, @@ -38,6 +41,10 @@ impl Dependency for ProvideDependency { &self.id } + fn loc(&self) -> Option { + Some(self.range.to_string()) + } + fn category(&self) -> &DependencyCategory { &DependencyCategory::Esm } @@ -116,7 +123,7 @@ impl DependencyTemplate for ProvideDependency { InitFragmentKey::ModuleExternal(format!("provided {}", self.identifier)), None, ))); - source.replace(self.start, self.end, &self.identifier, None); + source.replace(self.range.start, self.range.end, &self.identifier, None); } fn dependency_id(&self) -> Option { diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/provide_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/provide_plugin.rs index e5ec3d76613..3110da02358 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/provide_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/provide_plugin.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use once_cell::sync::OnceCell; use rspack_core::{ ApplyContext, CompilerOptions, ModuleType, NormalModuleFactoryParser, ParserAndGenerator, - ParserOptions, Plugin, PluginContext, SpanExt, + ParserOptions, Plugin, PluginContext, RealDependencyLocation, }; use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; @@ -13,11 +13,14 @@ use crate::{ dependency::ProvideDependency, parser_and_generator::JavaScriptParserAndGenerator, visitors::JavascriptParser, BoxJavascriptParserPlugin, }; - const SOURCE_DOT: &str = r#"."#; const MODULE_DOT: &str = r#"_dot_"#; -fn dep(value: &ProvideValue, name: &str, start: u32, end: u32) -> Option { +fn create_provide_dep( + name: &str, + value: &ProvideValue, + range: RealDependencyLocation, +) -> Option { if let Some(requests) = value.get(name) { let name_identifier = if name.contains(SOURCE_DOT) { format!("__webpack_provide_{}", name.replace(SOURCE_DOT, MODULE_DOT)) @@ -25,8 +28,7 @@ fn dep(value: &ProvideValue, name: &str, start: u32, end: u32) -> Option Option { - dep( - &self.provide, + let range: RealDependencyLocation = expr.callee.span().into(); + create_provide_dep( for_name, - expr.callee.span().real_lo(), - expr.callee.span().real_hi(), + &self.provide, + range.with_source(parser.source_map.clone()), ) .map(|dep| { parser.dependencies.push(Box::new(dep)); @@ -104,11 +106,11 @@ impl JavascriptParserPlugin for ProvidePlugin { expr: &swc_core::ecma::ast::MemberExpr, for_name: &str, ) -> Option { - dep( - &self.provide, + let range: RealDependencyLocation = expr.span().into(); + create_provide_dep( for_name, - expr.span().real_lo(), - expr.span().real_hi(), + &self.provide, + range.with_source(parser.source_map.clone()), ) .map(|dep| { parser.dependencies.push(Box::new(dep)); @@ -122,11 +124,11 @@ impl JavascriptParserPlugin for ProvidePlugin { ident: &swc_core::ecma::ast::Ident, for_name: &str, ) -> Option { - dep( - &self.provide, + let range: RealDependencyLocation = ident.span.into(); + create_provide_dep( for_name, - ident.span.real_lo(), - ident.span.real_hi(), + &self.provide, + range.with_source(parser.source_map.clone()), ) .map(|dep| { parser.dependencies.push(Box::new(dep)); From b0f8efb626738416ad7ed15a340b1886ecde4cc8 Mon Sep 17 00:00:00 2001 From: dalaoshu Date: Fri, 13 Sep 2024 18:17:47 +0800 Subject: [PATCH 45/51] fix(rspack-test-tools): corret script `testu` (#7894) --- packages/rspack-test-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rspack-test-tools/package.json b/packages/rspack-test-tools/package.json index 9eec08f187a..08b98b87f44 100644 --- a/packages/rspack-test-tools/package.json +++ b/packages/rspack-test-tools/package.json @@ -19,7 +19,7 @@ "dev:viewer": "rspack serve", "dev": "tsc -b -w", "test": "sh -c 'run-s \"test:* -- $*\"' sh", - "testu": "sh -c 'run-s \"test:* -u -- $*\"' sh", + "testu": "sh -c 'run-s \"test:* -- -u $*\"' sh", "test:base": "cross-env NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.js --passWithNoTests", "test:hot": "cross-env RSPACK_HOT_TEST=true NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.hot.js --passWithNoTests", "test:diff": "cross-env RSPACK_DIFF=true NO_COLOR=1 node --expose-gc --max-old-space-size=8192 --experimental-vm-modules ../../node_modules/jest-cli/bin/jest --logHeapUsage --colors --config ./jest.config.diff.js --passWithNoTests", From fc3b9fa4c95c8cddd67026548901c2debb56d213 Mon Sep 17 00:00:00 2001 From: harpsealjs Date: Fri, 13 Sep 2024 20:03:28 +0800 Subject: [PATCH 46/51] feat: support custom handler of progress plugin (#7896) --- crates/node_binding/binding.d.ts | 9 +- .../src/options/raw_builtins/raw_progress.rs | 50 ++-- crates/rspack_plugin_progress/src/lib.rs | 216 +++++++++++------- packages/rspack-dev-server/jest.config.js | 2 - packages/rspack-dev-server/src/server.ts | 5 - packages/rspack/etc/api.md | 2 +- .../src/builtin-plugin/ProgressPlugin.ts | 22 +- 7 files changed, 186 insertions(+), 120 deletions(-) diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 8ebb2815109..ddfc7990553 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1599,11 +1599,12 @@ export interface RawPathData { } export interface RawProgressPluginOptions { - prefix: string - profile: boolean - template: string + prefix?: string + profile?: boolean + template?: string tick?: string | Array - progressChars: string + progressChars?: string + handler?: (percent: number, msg: string, items: string[]) => void } export interface RawProvideOptions { diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_progress.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_progress.rs index 46de539935a..a3c4bfdda4e 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/raw_progress.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_progress.rs @@ -1,33 +1,51 @@ +use std::sync::Arc; + +use derivative::Derivative; use napi::Either; use napi_derive::napi; -use rspack_plugin_progress::ProgressPluginOptions; +use rspack_napi::threadsafe_function::ThreadsafeFunction; +use rspack_plugin_progress::{ProgressPluginDisplayOptions, ProgressPluginOptions}; -#[derive(Debug, Clone)] -#[napi(object)] +type HandlerFn = ThreadsafeFunction<(f64, String, Vec), ()>; +#[derive(Derivative)] +#[derivative(Debug)] +#[napi(object, object_to_js = false)] pub struct RawProgressPluginOptions { // the prefix name of progress bar - pub prefix: String, + pub prefix: Option, // tells ProgressPlugin to collect profile data for progress steps. - pub profile: bool, + pub profile: Option, // the template of progress bar - pub template: String, + pub template: Option, // the tick string sequence for spinners, if it's string then it will be split into characters pub tick: Option>>, // the progress characters - pub progress_chars: String, + pub progress_chars: Option, + // the handler for progress event + #[derivative(Debug = "ignore")] + #[napi(ts_type = "(percent: number, msg: string, items: string[]) => void")] + pub handler: Option, } impl From for ProgressPluginOptions { fn from(value: RawProgressPluginOptions) -> Self { - Self { - prefix: value.prefix, - profile: value.profile, - template: value.template, - progress_chars: value.progress_chars, - tick_strings: value.tick.map(|tick| match tick { - Either::A(str) => str.chars().map(|c| c.to_string()).collect(), - Either::B(vec) => vec, - }), + if let Some(f) = value.handler { + Self::Handler(Arc::new(move |percent, msg, items| { + f.blocking_call_with_sync((percent, msg, items)) + })) + } else { + Self::Default(ProgressPluginDisplayOptions { + prefix: value.prefix.unwrap_or_default(), + profile: value.profile.unwrap_or_default(), + template: value.template.unwrap_or( + "● {prefix:.bold} {bar:25.green/white.dim} ({percent}%) {wide_msg:.dim}".to_string(), + ), + progress_chars: value.progress_chars.unwrap_or("━━".to_string()), + tick_strings: value.tick.map(|tick| match tick { + Either::A(str) => str.chars().map(|c| c.to_string()).collect(), + Either::B(vec) => vec, + }), + }) } } } diff --git a/crates/rspack_plugin_progress/src/lib.rs b/crates/rspack_plugin_progress/src/lib.rs index 30935524791..b7d822c9c2e 100644 --- a/crates/rspack_plugin_progress/src/lib.rs +++ b/crates/rspack_plugin_progress/src/lib.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use std::sync::atomic::Ordering::Relaxed; -use std::sync::RwLock; -use std::time::Duration; +use std::sync::{Arc, RwLock}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use std::{cmp, sync::atomic::AtomicU32, time::Instant}; use async_trait::async_trait; @@ -20,8 +20,29 @@ use rspack_core::{ use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; -#[derive(Debug, Clone, Default)] -pub struct ProgressPluginOptions { +type HandlerFn = Arc) -> Result<()> + Send + Sync>; + +pub enum ProgressPluginOptions { + Handler(HandlerFn), + Default(ProgressPluginDisplayOptions), +} + +impl std::fmt::Debug for ProgressPluginOptions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProgressPluginOptions::Handler(_handler) => { + f.debug_struct("ProgressPluginOptions::Handler").finish() + } + ProgressPluginOptions::Default(options) => f + .debug_struct("ProgressPluginOptions::Default") + .field("options", &options) + .finish(), + } + } +} + +#[derive(Debug, Default)] +pub struct ProgressPluginDisplayOptions { // the prefix name of progress bar pub prefix: String, // tells ProgressPlugin to collect profile data for progress steps. @@ -45,33 +66,41 @@ pub struct ProgressPluginStateInfo { #[derive(Debug)] pub struct ProgressPlugin { pub options: ProgressPluginOptions, - pub progress_bar: ProgressBar, + pub progress_bar: Option, pub modules_count: AtomicU32, pub modules_done: AtomicU32, pub active_modules: RwLock>, pub last_modules_count: RwLock>, pub last_active_module: RwLock>, pub last_state_info: RwLock>, + pub last_updated: AtomicU32, } impl ProgressPlugin { pub fn new(options: ProgressPluginOptions) -> Self { - // default interval is 20, means draw every 1000/20 = 50ms, use 100 to draw every 1000/100 = 10ms - let progress_bar = - ProgressBar::with_draw_target(Some(100), ProgressDrawTarget::stdout_with_hz(100)); - let mut progress_bar_style = ProgressStyle::with_template(&options.template) - .expect("TODO:") - .progress_chars(&options.progress_chars); - if let Some(tick_strings) = &options.tick_strings { - progress_bar_style = progress_bar_style.tick_strings( - tick_strings - .iter() - .map(|s| s.as_str()) - .collect::>() - .as_slice(), - ); - } - progress_bar.set_style(progress_bar_style); + let progress_bar = match &options { + ProgressPluginOptions::Handler(_fn) => None, + ProgressPluginOptions::Default(options) => { + // default interval is 20, means draw every 1000/20 = 50ms, use 100 to draw every 1000/100 = 10ms + let progress_bar = + ProgressBar::with_draw_target(Some(100), ProgressDrawTarget::stdout_with_hz(100)); + + let mut progress_bar_style = ProgressStyle::with_template(&options.template) + .expect("TODO:") + .progress_chars(&options.progress_chars); + if let Some(tick_strings) = &options.tick_strings { + progress_bar_style = progress_bar_style.tick_strings( + tick_strings + .iter() + .map(|s| s.as_str()) + .collect::>() + .as_slice(), + ); + } + progress_bar.set_style(progress_bar_style); + Some(progress_bar) + } + }; Self::new_inner( options, @@ -82,16 +111,31 @@ impl ProgressPlugin { Default::default(), Default::default(), Default::default(), + AtomicU32::new(0), ) } - fn update(&self) { + fn update_throttled(&self) -> Result<()> { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("failed to get current time") + .as_millis() as u32; + + if current_time - self.last_updated.load(Relaxed) > 200 { + self.update()?; + self.last_updated.store(current_time, Relaxed); + } + + Ok(()) + } + + fn update(&self) -> Result<()> { let modules_done = self.modules_done.load(Relaxed); - let percent_by_module = (modules_done as f32) + let percent_by_module = (modules_done as f64) / (cmp::max( self.last_modules_count.read().expect("TODO:").unwrap_or(1), self.modules_count.load(Relaxed), - ) as f32); + ) as f64); let mut items = vec![]; let last_active_module = self.last_active_module.read().expect("TODO:"); @@ -109,25 +153,32 @@ impl ProgressPlugin { String::from("building"), items, duration, - ); + )?; } + Ok(()) } pub fn handler( &self, - percent: f32, + percent: f64, msg: String, state_items: Vec, time: Option, - ) { - if self.options.profile { - self.default_handler(percent, msg, state_items, time); - } else { - self.progress_bar_handler(percent, msg, state_items); - } + ) -> Result<()> { + match &self.options { + ProgressPluginOptions::Handler(handler) => handler(percent, msg, state_items)?, + ProgressPluginOptions::Default(options) => { + if options.profile { + self.default_handler(percent, msg, state_items, time); + } else { + self.progress_bar_handler(percent, msg, state_items); + } + } + }; + Ok(()) } - fn default_handler(&self, _: f32, msg: String, items: Vec, duration: Option) { + fn default_handler(&self, _: f64, msg: String, items: Vec, duration: Option) { let full_state = [vec![msg.clone()], items.clone()].concat(); let now = Instant::now(); { @@ -194,21 +245,28 @@ impl ProgressPlugin { } } - fn progress_bar_handler(&self, percent: f32, msg: String, state_items: Vec) { - self - .progress_bar - .set_message(msg + " " + state_items.join(" ").as_str()); - self.progress_bar.set_position((percent * 100.0) as u64); + fn progress_bar_handler(&self, percent: f64, msg: String, state_items: Vec) { + if let Some(progress_bar) = &self.progress_bar { + progress_bar.set_message(msg + " " + state_items.join(" ").as_str()); + progress_bar.set_position((percent * 100.0) as u64); + } } - fn sealing_hooks_report(&self, name: &str, index: i32) { + fn sealing_hooks_report(&self, name: &str, index: i32) -> Result<()> { let number_of_sealing_hooks = 38; self.handler( - 0.7 + 0.25 * (index / number_of_sealing_hooks) as f32, + 0.7 + 0.25 * (index / number_of_sealing_hooks) as f64, "sealing".to_string(), vec![name.to_string()], None, - ); + ) + } + + pub fn is_profile(&self) -> bool { + match &self.options { + ProgressPluginOptions::Handler(_) => false, + ProgressPluginOptions::Default(options) => options.profile, + } } } @@ -223,8 +281,7 @@ async fn this_compilation( "setup".to_string(), vec!["compilation".to_string()], None, - ); - Ok(()) + ) } #[plugin_hook(CompilerCompilation for ProgressPlugin)] @@ -238,17 +295,20 @@ async fn compilation( "setup".to_string(), vec!["compilation".to_string()], None, - ); - Ok(()) + ) } #[plugin_hook(CompilerMake for ProgressPlugin)] async fn make(&self, _compilation: &mut Compilation) -> Result<()> { - if !self.options.profile { - self.progress_bar.reset(); - self.progress_bar.set_prefix(self.options.prefix.clone()); + if let ProgressPluginOptions::Default(options) = &self.options { + let progress_bar = self.progress_bar.as_ref().unwrap_or_else(|| unreachable!()); + if !options.profile { + progress_bar.reset(); + progress_bar.set_prefix(options.prefix.clone()); + } } - self.handler(0.01, String::from("make"), vec![], None); + + self.handler(0.01, String::from("make"), vec![], None)?; self.modules_count.store(0, Relaxed); self.modules_done.store(0, Relaxed); Ok(()) @@ -267,9 +327,12 @@ async fn build_module(&self, module: &mut BoxModule) -> Result<()> { .write() .expect("TODO:") .replace(module.identifier()); - if !self.options.profile { - self.update(); + if let ProgressPluginOptions::Default(options) = &self.options { + if !options.profile { + self.update_throttled()?; + } } + Ok(()) } @@ -283,8 +346,8 @@ async fn succeed_module(&self, module: &mut BoxModule) -> Result<()> { .replace(module.identifier()); // only profile mode should update at succeed module - if self.options.profile { - self.update(); + if self.is_profile() { + self.update_throttled()?; } let mut last_active_module = Default::default(); { @@ -292,20 +355,20 @@ async fn succeed_module(&self, module: &mut BoxModule) -> Result<()> { active_modules.remove(&module.identifier()); // get the last active module - if !self.options.profile { + if !self.is_profile() { active_modules.iter().for_each(|(module, _)| { last_active_module = *module; }); } } - if !self.options.profile { + if !self.is_profile() { self .last_active_module .write() .expect("TODO:") .replace(last_active_module); if !last_active_module.is_empty() { - self.update(); + self.update_throttled()?; } } Ok(()) @@ -318,86 +381,76 @@ async fn finish_make(&self, _compilation: &mut Compilation) -> Result<()> { "building".to_string(), vec!["finish make".to_string()], None, - ); - Ok(()) + ) } #[plugin_hook(CompilationSeal for ProgressPlugin)] async fn seal(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("plugins", 1); - Ok(()) + self.sealing_hooks_report("plugins", 1) } #[plugin_hook(CompilationOptimizeDependencies for ProgressPlugin)] fn optimize_dependencies(&self, _compilation: &mut Compilation) -> Result> { - self.sealing_hooks_report("dependencies", 2); + self.sealing_hooks_report("dependencies", 2)?; Ok(None) } #[plugin_hook(CompilationFinishModules for ProgressPlugin)] async fn finish_modules(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("finish modules", 0); - Ok(()) + self.sealing_hooks_report("finish modules", 0) } #[plugin_hook(CompilationOptimizeModules for ProgressPlugin)] async fn optimize_modules(&self, _compilation: &mut Compilation) -> Result> { - self.sealing_hooks_report("module optimization", 7); + self.sealing_hooks_report("module optimization", 7)?; Ok(None) } #[plugin_hook(CompilationAfterOptimizeModules for ProgressPlugin)] async fn after_optimize_modules(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("after module optimization", 8); - Ok(()) + self.sealing_hooks_report("after module optimization", 8) } #[plugin_hook(CompilationOptimizeChunks for ProgressPlugin)] fn optimize_chunks(&self, _compilation: &mut Compilation) -> Result> { - self.sealing_hooks_report("chunk optimization", 9); + self.sealing_hooks_report("chunk optimization", 9)?; Ok(None) } #[plugin_hook(CompilationOptimizeTree for ProgressPlugin)] async fn optimize_tree(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("module and chunk tree optimization", 11); - Ok(()) + self.sealing_hooks_report("module and chunk tree optimization", 11) } #[plugin_hook(CompilationOptimizeChunkModules for ProgressPlugin)] async fn optimize_chunk_modules(&self, _compilation: &mut Compilation) -> Result> { - self.sealing_hooks_report("chunk modules optimization", 13); + self.sealing_hooks_report("chunk modules optimization", 13)?; Ok(None) } #[plugin_hook(CompilationModuleIds for ProgressPlugin)] fn module_ids(&self, _modules: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("module ids", 16); - Ok(()) + self.sealing_hooks_report("module ids", 16) } #[plugin_hook(CompilationChunkIds for ProgressPlugin)] fn chunk_ids(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("chunk ids", 21); - Ok(()) + self.sealing_hooks_report("chunk ids", 21) } #[plugin_hook(CompilationProcessAssets for ProgressPlugin, stage = Compilation::PROCESS_ASSETS_STAGE_ADDITIONAL)] async fn process_assets(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("asset processing", 35); - Ok(()) + self.sealing_hooks_report("asset processing", 35) } #[plugin_hook(CompilationAfterProcessAssets for ProgressPlugin)] async fn after_process_assets(&self, _compilation: &mut Compilation) -> Result<()> { - self.sealing_hooks_report("after asset optimization", 36); - Ok(()) + self.sealing_hooks_report("after asset optimization", 36) } #[plugin_hook(CompilerEmit for ProgressPlugin)] async fn emit(&self, _compilation: &mut Compilation) -> Result<()> { - self.handler(0.98, "emitting".to_string(), vec!["emit".to_string()], None); - Ok(()) + self.handler(0.98, "emitting".to_string(), vec!["emit".to_string()], None) } #[plugin_hook(CompilerAfterEmit for ProgressPlugin)] @@ -407,8 +460,7 @@ async fn after_emit(&self, _compilation: &mut Compilation) -> Result<()> { "emitting".to_string(), vec!["after emit".to_string()], None, - ); - Ok(()) + ) } #[async_trait] diff --git a/packages/rspack-dev-server/jest.config.js b/packages/rspack-dev-server/jest.config.js index 0b27f291c61..088a369cca9 100644 --- a/packages/rspack-dev-server/jest.config.js +++ b/packages/rspack-dev-server/jest.config.js @@ -17,8 +17,6 @@ const config = { "/tests/e2e/allowed-hosts.test.js", // TODO: check why this test timeout "/tests/e2e/host.test.js", - // TODO: not support progress plugin event yet - "/tests/e2e/progress.test.js", // TODO: check why this test throw error when run with other tests "/tests/e2e/watch-files.test.js", // TODO: check why this test timeout diff --git a/packages/rspack-dev-server/src/server.ts b/packages/rspack-dev-server/src/server.ts index 610145b8562..e027a3d49f3 100644 --- a/packages/rspack-dev-server/src/server.ts +++ b/packages/rspack-dev-server/src/server.ts @@ -97,11 +97,6 @@ export class RspackDevServer extends WebpackDevServer { } } - if (typeof this.options.client === "object") { - // TODO: support progress plugin event - this.options.client.progress = false; - } - // @ts-expect-error await super.initialize(); } diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index 054e7b1cce6..002f97785fe 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -9545,7 +9545,7 @@ export const ProgressPlugin: { }; // @public (undocumented) -export type ProgressPluginArgument = Partial | undefined; +export type ProgressPluginArgument = Partial> | ((percentage: number, msg: string, ...args: string[]) => void) | undefined; // @public (undocumented) export const ProvidePlugin: { diff --git a/packages/rspack/src/builtin-plugin/ProgressPlugin.ts b/packages/rspack/src/builtin-plugin/ProgressPlugin.ts index 88daa12ec38..ae95d55e49d 100644 --- a/packages/rspack/src/builtin-plugin/ProgressPlugin.ts +++ b/packages/rspack/src/builtin-plugin/ProgressPlugin.ts @@ -6,17 +6,19 @@ import { import { create } from "./base"; export type ProgressPluginArgument = - | Partial + | Partial> + | ((percentage: number, msg: string, ...args: string[]) => void) | undefined; export const ProgressPlugin = create( BuiltinPluginName.ProgressPlugin, - (progress: ProgressPluginArgument = {}): RawProgressPluginOptions => ({ - prefix: progress.prefix ?? "", - profile: progress.profile ?? false, - template: - progress.template ?? - "● {prefix:.bold} {bar:25.green/white.dim} ({percent}%) {wide_msg:.dim}", - tick: progress.tick, - progressChars: progress.progressChars ?? "━━" - }) + (progress: ProgressPluginArgument = {}): RawProgressPluginOptions => { + if (typeof progress === "function") { + return { + handler: (percentage, msg, items) => { + progress(percentage, msg, ...items); + } + }; + } + return progress; + } ); From 8093c9f55aaa7b8c3856a827d768de12f775f300 Mon Sep 17 00:00:00 2001 From: Soon Date: Fri, 13 Sep 2024 21:20:12 +0800 Subject: [PATCH 47/51] docs(landingpage): update @rstack-dev/doc-ui to 1.5.1 (#7899) * docs(landingpage): update @rstack-dev/doc-ui * docs: adjust some logo size --- website/package.json | 2 +- website/pnpm-lock.yaml | 10 ++--- .../theme/components/Announcement/close.tsx | 18 -------- .../components/Announcement/index.module.scss | 28 ------------ .../theme/components/Announcement/index.tsx | 41 ------------------ .../Landingpage/WhoIsUsing/index.tsx | 4 +- website/theme/index.tsx | 43 +++++++++++++------ website/theme/pages/index.tsx | 10 ----- 8 files changed, 38 insertions(+), 118 deletions(-) delete mode 100644 website/theme/components/Announcement/close.tsx delete mode 100644 website/theme/components/Announcement/index.module.scss delete mode 100644 website/theme/components/Announcement/index.tsx diff --git a/website/package.json b/website/package.json index 003628a37db..87ec40615f2 100644 --- a/website/package.json +++ b/website/package.json @@ -19,7 +19,7 @@ "license": "MIT", "packageManager": "pnpm@9.3.0", "dependencies": { - "@rstack-dev/doc-ui": "1.4.2", + "@rstack-dev/doc-ui": "1.5.1", "antd": "5.19.3", "axios": "^1.6.1", "date-fns": "^2.29.3", diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index 471505419e9..73806d6836f 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@rstack-dev/doc-ui': - specifier: 1.4.2 - version: 1.4.2(@emotion/is-prop-valid@0.8.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: 1.5.1 + version: 1.5.1(@emotion/is-prop-valid@0.8.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) antd: specifier: 5.19.3 version: 5.19.3(date-fns@2.30.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) @@ -710,8 +710,8 @@ packages: resolution: {integrity: sha512-bwZldJkS0tZfPrS04JAgrhuvGU81fOZ1BTd/wgPCRXJshuU5nBo01JHoq3D8WaTa4wtRQKo0i1yMs6pS+Xu0EA==} engines: {node: '>=14.17.6'} - '@rstack-dev/doc-ui@1.4.2': - resolution: {integrity: sha512-dC1L65hIqzO0OwPtKXf7Y+wJJCy5FudEibUf8XW9AAaTM47/izBR768zTdhv+IIwk+40ZsBp7jnIIEsR05BnNQ==} + '@rstack-dev/doc-ui@1.5.1': + resolution: {integrity: sha512-uQSMgPnHE8TC7c9v78+Kg8SxdfP3cK8+47lXvO7W6j7jzG1q1YKFyYRpNFmanODGFUHvWAYru+T4W6nyVhbJ7g==} '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} @@ -3953,7 +3953,7 @@ snapshots: react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rspack-plugin-virtual-module: 0.1.12 - '@rstack-dev/doc-ui@1.4.2(@emotion/is-prop-valid@0.8.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@rstack-dev/doc-ui@1.5.1(@emotion/is-prop-valid@0.8.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: framer-motion: 11.3.4(@emotion/is-prop-valid@0.8.8)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) transitivePeerDependencies: diff --git a/website/theme/components/Announcement/close.tsx b/website/theme/components/Announcement/close.tsx deleted file mode 100644 index b97f681465c..00000000000 --- a/website/theme/components/Announcement/close.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type * as React from 'react'; - -function IconClose(props: React.SVGProps) { - return ( - - IconClose - - - ); -} - -export default IconClose; diff --git a/website/theme/components/Announcement/index.module.scss b/website/theme/components/Announcement/index.module.scss deleted file mode 100644 index 277dededafa..00000000000 --- a/website/theme/components/Announcement/index.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -.bar { - display: flex; - justify-content: center; - align-items: center; - color: #fff; - background-image: var( - --orange-gradient, - linear-gradient( - 279deg, - hsl(32.71, 100%, 50%) 35.21%, - hsl(6.91, 94.76%, 67%) 63.34% - ) - ); - height: 36px; -} - -.close { - right: 1.5rem; - font-size: 28px; - padding: 5px; - position: absolute; - cursor: pointer; - border-radius: 4px; - - &:hover { - background-color: rgba(255, 255, 255, 0.3); - } -} diff --git a/website/theme/components/Announcement/index.tsx b/website/theme/components/Announcement/index.tsx deleted file mode 100644 index 4fff12e0465..00000000000 --- a/website/theme/components/Announcement/index.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useState } from 'react'; -import { usePageData } from 'rspress/runtime'; -import { useLang } from 'rspress/runtime'; -import IconCloseCircle from './close'; -import styles from './index.module.scss'; - -const LOCAL_STORAGE_KEY = 'rspack-announcement-closed'; -const ANNOUNCEMENT_URL = '/blog/announcing-1-0'; - -export function Announcement() { - const [disable, setDisable] = useState( - window.localStorage.getItem(LOCAL_STORAGE_KEY) ?? false, - ); - const { page } = usePageData(); - const lang = useLang(); - - // Only display in homepage - if (page.pageType !== 'home' || disable) { - return null; - } - - return ( -
- - {lang === 'en' - ? 'Rspack v1.0 has been released!' - : 'Rspack v1.0 正式发布!'} - - { - setDisable(true); - window.localStorage.setItem(LOCAL_STORAGE_KEY, 'true'); - }} - className={styles.close} - /> -
- ); -} diff --git a/website/theme/components/Landingpage/WhoIsUsing/index.tsx b/website/theme/components/Landingpage/WhoIsUsing/index.tsx index dc55393a6fd..2e9af4784ed 100644 --- a/website/theme/components/Landingpage/WhoIsUsing/index.tsx +++ b/website/theme/components/Landingpage/WhoIsUsing/index.tsx @@ -43,7 +43,7 @@ const companyList: Company[] = [ name: 'Amazon', logo: amazonLogo, url: 'https://amazon.com/', - width: 120, + width: 110, }, { name: 'ByteDance', @@ -67,7 +67,7 @@ const companyList: Company[] = [ name: 'NIO', logo: nioLogo, url: 'https://nio.com', - width: 115, + width: 110, }, { name: 'ABB', diff --git a/website/theme/index.tsx b/website/theme/index.tsx index 3044a555470..9f3c22eef91 100644 --- a/website/theme/index.tsx +++ b/website/theme/index.tsx @@ -1,20 +1,37 @@ +// enable this when we need a new announcement +import { Announcement } from '@rstack-dev/doc-ui/announcement'; import { NavIcon } from '@rstack-dev/doc-ui/nav-icon'; -import { NoSSR } from 'rspress/runtime'; +import { NoSSR, useLang, usePageData } from 'rspress/runtime'; import Theme from 'rspress/theme'; -// enable this when we need a new announcement -import { Announcement } from './components/Announcement'; import { HomeLayout } from './pages'; -const Layout = () => ( - } - beforeNav={ - - - - } - /> -); +const ANNOUNCEMENT_URL = '/blog/announcing-1-0'; + +const Layout = () => { + const { page } = usePageData(); + const lang = useLang(); + return ( + } + beforeNav={ + + + + } + /> + ); +}; export * from 'rspress/theme'; diff --git a/website/theme/pages/index.tsx b/website/theme/pages/index.tsx index b25547707f7..ab8d7265e10 100644 --- a/website/theme/pages/index.tsx +++ b/website/theme/pages/index.tsx @@ -26,15 +26,5 @@ export function HomeLayout() { - // - // - // - // - // - // - // } - // /> ); } From 5ea4a167c330cee678bec1541e208b81cb75d14a Mon Sep 17 00:00:00 2001 From: Soon Date: Fri, 13 Sep 2024 21:36:56 +0800 Subject: [PATCH 48/51] docs: update node version and webpack-dev-server@5 (#7900) Co-authored-by: neverland Co-authored-by: hardfist --- website/docs/en/guide/features/dev-server.mdx | 16 +++++++++++++--- website/docs/en/guide/start/quick-start.mdx | 2 ++ website/docs/zh/guide/features/dev-server.mdx | 16 +++++++++++++--- website/docs/zh/guide/start/quick-start.mdx | 2 ++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/website/docs/en/guide/features/dev-server.mdx b/website/docs/en/guide/features/dev-server.mdx index 835d66f7ecd..e5f465b427d 100644 --- a/website/docs/en/guide/features/dev-server.mdx +++ b/website/docs/en/guide/features/dev-server.mdx @@ -2,6 +2,15 @@ Rspack CLI comes with a built-in `@rspack/dev-server` for development and debugging. Its capabilities are similar to `webpack-dev-server`, including features like Hot Module Replacement (HMR), proxy server and more. +:::tip + +`webpack-dev-server@5` is used in `@rspack/dev-server`, which has some differences from `webpack-dev-server@4`. + +- The minimum supported Node.js version for webpack-dev-server v5 is 18.12.0. +- Several configuration options have changed. Please refer to the [webpack-dev-server v5 migration guide](https://github.com/webpack/webpack-dev-server/blob/master/migration-v5.md). + +::: + ### HMR By default, Rspack enables HMR in dev mode. You can disable HMR by configuring the `devServer.hot` option in `rspack.config.js`. @@ -25,12 +34,13 @@ Rspack has a built-in simple proxy server. You can enable the proxy server by co ```js module.exports = { devServer: { - proxy: { - '/api': { + proxy: [ + { + context: ['/api'], target: 'http://localhost:3000', changeOrigin: true, }, - }, + ], }, }; ``` diff --git a/website/docs/en/guide/start/quick-start.mdx b/website/docs/en/guide/start/quick-start.mdx index 55b4811cf0e..3c6ade4b543 100644 --- a/website/docs/en/guide/start/quick-start.mdx +++ b/website/docs/en/guide/start/quick-start.mdx @@ -45,6 +45,8 @@ We recommend using [Rsbuild](https://rsbuild.dev/) to create new projects, simpl Rspack CLI is a tool comparable to Webpack CLI, offering the basic `serve` and `build` commands. +Rsbuild supports Node.js >= 16, but Rspack CLI requires Node.js version >= 18.12.0. + Run the following command to create an Rspack CLI project: diff --git a/website/docs/zh/guide/features/dev-server.mdx b/website/docs/zh/guide/features/dev-server.mdx index 857f3d808f6..55ae378eae8 100644 --- a/website/docs/zh/guide/features/dev-server.mdx +++ b/website/docs/zh/guide/features/dev-server.mdx @@ -2,6 +2,15 @@ Rspack CLI 内置了 `@rspack/dev-server` 用于开发调试,它的能力与 `webpack-dev-server` 相似,内置了 HMR、代理服务器等功能。 +:::tip + +`@rspack/dev-server` 中使用了 `webpack-dev-server@5` ,与 `webpack-dev-server@4` 有一些差异。 + +- webpack-dev-server v5 最低支持的 Node.js 版本为 18.12.0。 +- 配置选项发生了若干变更,请参阅 [webpack-dev-server v5 的迁移指南](https://github.com/webpack/webpack-dev-server/blob/master/migration-v5.md)。 + +::: + ### HMR Rspack 在 dev 模式下默认开启了 HMR,你也可以在 `rspack.config.js` 中配置 `devServer.hot` 选项来关闭 HMR。 @@ -25,12 +34,13 @@ Rspack 内置了一个简单的代理服务器,你可以在 `rspack.config.js` ```js module.exports = { devServer: { - proxy: { - '/api': { + proxy: [ + { + context: ['/api'], target: 'http://localhost:3000', changeOrigin: true, }, - }, + ], }, }; ``` diff --git a/website/docs/zh/guide/start/quick-start.mdx b/website/docs/zh/guide/start/quick-start.mdx index b85e5cc74e9..64fdb6d5561 100644 --- a/website/docs/zh/guide/start/quick-start.mdx +++ b/website/docs/zh/guide/start/quick-start.mdx @@ -45,6 +45,8 @@ Rsbuild 是由 Rspack 驱动的高性能构建工具,由 Rspack 团队开发 Rspack CLI 是对标 Webpack CLI 的工具,提供基础的 `serve` 和 `build` 构建命令。 +Rsbuild 支持 Node.js >= 16,但是 Rspack CLI 要求 Node.js 版本 >= 18.12.0 + 执行如下命令即可创建基于 Rspack CLI 的项目: From 0325a3d0717afb7ad4a8f1ba23dbfe96eae499e1 Mon Sep 17 00:00:00 2001 From: Gengkun Date: Fri, 13 Sep 2024 22:26:08 +0800 Subject: [PATCH 49/51] fix: should run watch cases test in build assets (#7897) --- packages/rspack-test-tools/etc/api.md | 4 ++ .../rspack-test-tools/src/processor/watch.ts | 52 ++++++++++++++++- .../src/runner/runner/watch.ts | 1 + .../tests/NewIncremental.test.js | 1 + .../__snapshots__/web/0.snap.txt | 12 ---- .../__snapshots__/web/1.snap.txt | 58 ------------------- .../index.js | 16 ----- .../module.js | 6 -- .../compiler/multiply-compiler/0/entry1.js | 2 +- .../compiler/multiply-compiler/0/entry2.js | 2 +- .../multiply-compiler/rspack.config.js | 6 ++ .../compiler/multiply-compiler/test.config.js | 10 ++++ .../context/out-of-context/test.config.js | 4 +- .../side-effects/basic/0}/foo.js | 0 .../watchCases/side-effects/basic/0/index.js | 5 ++ .../watchCases/side-effects/basic/0/module.js | 3 + .../side-effects/basic/0}/package.json | 0 .../side-effects/basic/0}/reexports-deep.js | 0 .../side-effects/basic/0}/reexports.js | 0 .../watchCases/side-effects/basic/1/module.js | 2 + .../side-effects/basic}/rspack.config.js | 5 -- 21 files changed, 86 insertions(+), 103 deletions(-) delete mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt delete mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt delete mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js delete mode 100644 packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js create mode 100644 packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/test.config.js rename packages/rspack-test-tools/tests/{hotCases/newTreeshaking/re-export-optimization-for-inaffected-module => watchCases/side-effects/basic/0}/foo.js (100%) create mode 100644 packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/index.js create mode 100644 packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/module.js rename packages/rspack-test-tools/tests/{hotCases/newTreeshaking/re-export-optimization-for-inaffected-module => watchCases/side-effects/basic/0}/package.json (100%) rename packages/rspack-test-tools/tests/{hotCases/newTreeshaking/re-export-optimization-for-inaffected-module => watchCases/side-effects/basic/0}/reexports-deep.js (100%) rename packages/rspack-test-tools/tests/{hotCases/newTreeshaking/re-export-optimization-for-inaffected-module => watchCases/side-effects/basic/0}/reexports.js (100%) create mode 100644 packages/rspack-test-tools/tests/watchCases/side-effects/basic/1/module.js rename packages/rspack-test-tools/tests/{hotCases/newTreeshaking/re-export-optimization-for-inaffected-module => watchCases/side-effects/basic}/rspack.config.js (67%) diff --git a/packages/rspack-test-tools/etc/api.md b/packages/rspack-test-tools/etc/api.md index 1f51e670b37..aecae54935a 100644 --- a/packages/rspack-test-tools/etc/api.md +++ b/packages/rspack-test-tools/etc/api.md @@ -1496,8 +1496,12 @@ export class WatchProcessor extends MultiTaskProcessor< // (undocumented) compiler(context: ITestContext): Promise; // (undocumented) + config(context: ITestContext): Promise; + // (undocumented) protected currentTriggerFilename: string | null; // (undocumented) + static findBundle(index: number, context: ITestContext, options: TCompilerOptions): string | string[]; + // (undocumented) protected lastHash: string | null; // (undocumented) static overrideOptions({ tempDir, name, experiments, optimization }: IWatchProcessorOptions): (index: number, context: ITestContext, options: TCompilerOptions) => void; diff --git a/packages/rspack-test-tools/src/processor/watch.ts b/packages/rspack-test-tools/src/processor/watch.ts index a081efd9183..f0e65c6b594 100644 --- a/packages/rspack-test-tools/src/processor/watch.ts +++ b/packages/rspack-test-tools/src/processor/watch.ts @@ -1,7 +1,9 @@ import fs from "node:fs"; import path from "node:path"; +import { merge } from "webpack-merge"; import { ECompilerEvent } from "../compiler"; +import { readConfigFile } from "../helper"; import copyDiff from "../helper/legacy/copyDiff"; import type { ECompilerType, @@ -9,7 +11,6 @@ import type { ITestEnv, TCompilerOptions } from "../type"; -import { ConfigProcessor } from "./config"; import { type IMultiTaskProcessorOptions, MultiTaskProcessor } from "./multi"; // This file is used to port step number to rspack.config.js/webpack.config.js @@ -38,7 +39,7 @@ export class WatchProcessor< constructor(protected _watchOptions: IWatchProcessorOptions) { super({ overrideOptions: WatchProcessor.overrideOptions(_watchOptions), - findBundle: ConfigProcessor.findBundle, + findBundle: WatchProcessor.findBundle, ..._watchOptions }); } @@ -93,6 +94,39 @@ export class WatchProcessor< ); } + async config(context: ITestContext) { + this.multiCompilerOptions = []; + const caseOptions: TCompilerOptions[] = Array.isArray( + this._multiOptions.configFiles + ) + ? readConfigFile( + this._multiOptions.configFiles!.map(i => context.getSource(i)) + ) + : [{}]; + + for (const [index, options] of caseOptions.entries()) { + const compilerOptions = merge( + typeof this._multiOptions.defaultOptions === "function" + ? this._multiOptions.defaultOptions!(index, context) + : {}, + options + ); + + if (typeof this._multiOptions.overrideOptions === "function") { + this._multiOptions.overrideOptions!(index, context, compilerOptions); + } + + this.multiCompilerOptions.push(compilerOptions); + } + + const compilerOptions = + this.multiCompilerOptions.length === 1 + ? this.multiCompilerOptions[0] + : this.multiCompilerOptions; + const compiler = this.getCompiler(context); + compiler.setOptions(compilerOptions as any); + } + static overrideOptions({ tempDir, name, @@ -108,6 +142,7 @@ export class WatchProcessor< if (!options.context) options.context = tempDir; if (!options.entry) options.entry = "./index.js"; if (!options.target) options.target = "async-node"; + if (!options.devtool) options.devtool = false; if (!options.output) options.output = {}; if (!options.output.path) options.output.path = context.getDist(); if (typeof options.output.pathinfo === "undefined") @@ -151,6 +186,19 @@ export class WatchProcessor< ).experiments!.rspackFuture!.bundlerInfo!.force ??= false; }; } + + static findBundle( + index: number, + context: ITestContext, + options: TCompilerOptions + ) { + const testConfig = context.getTestConfig(); + + if (typeof testConfig.findBundle === "function") { + return testConfig.findBundle!(index, options); + } + return "./bundle.js"; + } } export interface IWatchStepProcessorOptions diff --git a/packages/rspack-test-tools/src/runner/runner/watch.ts b/packages/rspack-test-tools/src/runner/runner/watch.ts index e1771299ce7..c301252d30f 100644 --- a/packages/rspack-test-tools/src/runner/runner/watch.ts +++ b/packages/rspack-test-tools/src/runner/runner/watch.ts @@ -33,6 +33,7 @@ export class WatchRunner< moduleScope.document = this.globalContext!.document; moduleScope.STATE = this.state; moduleScope.WATCH_STEP = this._watchOptions.stepName; + moduleScope.STATS_JSON = this._options.stats; return moduleScope; } diff --git a/packages/rspack-test-tools/tests/NewIncremental.test.js b/packages/rspack-test-tools/tests/NewIncremental.test.js index b99ef38778a..6d9656c9183 100644 --- a/packages/rspack-test-tools/tests/NewIncremental.test.js +++ b/packages/rspack-test-tools/tests/NewIncremental.test.js @@ -110,5 +110,6 @@ describeByWalk(v("watch (webpack-test)"), (name, src, dist) => { /caching-inner-source/, /production/, /warnings-contribute-to-hash/, + /issue-8766/, ] }); diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt deleted file mode 100644 index 6c0391979e0..00000000000 --- a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/0.snap.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Case re-export-optimization-for-inaffected-module: Step 0 - -## Changed Files - - -## Asset Files -- Bundle: bundle.js - -## Manifest - - -## Update \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt deleted file mode 100644 index 9306de4e18c..00000000000 --- a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/__snapshots__/web/1.snap.txt +++ /dev/null @@ -1,58 +0,0 @@ -# Case re-export-optimization-for-inaffected-module: Step 1 - -## Changed Files -- module.js - -## Asset Files -- Bundle: bundle.js -- Manifest: main.LAST_HASH.hot-update.json, size: 28 -- Update: main.LAST_HASH.hot-update.js, size: 616 - -## Manifest - -### main.LAST_HASH.hot-update.json - -```json -{"c":["main"],"r":[],"m":[]} -``` - - -## Update - - -### main.LAST_HASH.hot-update.js - -#### Changed Modules -- ./module.js - -#### Changed Runtime Modules -- webpack/runtime/get_full_hash - -#### Changed Content -```js -"use strict"; -self["webpackHotUpdate"]('main', { -"./module.js": (function (__unused_webpack_module, __webpack_exports__, __webpack_require__) { -__webpack_require__.r(__webpack_exports__); -__webpack_require__.d(__webpack_exports__, { - value: function() { return value; } -}); -/* harmony import */var _reexports__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./reexports */ "./foo.js"); - -const value = '' + _reexports__WEBPACK_IMPORTED_MODULE_0__.v; - - -}), - -},function(__webpack_require__) { -// webpack/runtime/get_full_hash -(() => { -__webpack_require__.h = function () { - return "CURRENT_HASH"; -}; - -})(); - -} -); -``` \ No newline at end of file diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js deleted file mode 100644 index 34ce108bd13..00000000000 --- a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import {value} from "./module"; - -let v = value; -module.hot.accept('./module', () => { - v = value -}); - -it("should auto-reexport an ES6 imported value on accept with newTreeshaking", async function (done) { - expect(v).toBe("foo"); - NEXT( - require("../../update")(done, true, () => { - expect(v).toBe("foo"); - done(); - }) - ); -}); diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js b/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js deleted file mode 100644 index de10e4d3031..00000000000 --- a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/module.js +++ /dev/null @@ -1,6 +0,0 @@ -import {v} from './reexports' -// -export const value = '' + v; ---- -import {v} from './reexports' -export const value = '' + v; diff --git a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry1.js b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry1.js index 0efb3f143e1..621a59b6809 100644 --- a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry1.js +++ b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry1.js @@ -1,5 +1,5 @@ import module from "./file"; -it("should watch for multiply compiler", function () { +it("should watch for multiply compiler (entry1)", function () { expect(module).toBe(WATCH_STEP); }); diff --git a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry2.js b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry2.js index 0efb3f143e1..42a9347391e 100644 --- a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry2.js +++ b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/0/entry2.js @@ -1,5 +1,5 @@ import module from "./file"; -it("should watch for multiply compiler", function () { +it("should watch for multiply compiler (entry2)", function () { expect(module).toBe(WATCH_STEP); }); diff --git a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/rspack.config.js b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/rspack.config.js index 0f58f4a4c16..00963fdccc8 100644 --- a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/rspack.config.js +++ b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/rspack.config.js @@ -3,11 +3,17 @@ module.exports = [ { entry: { entry1: "./entry1.js" + }, + output: { + filename: "./bundle1.js", } }, { entry: { entry2: "./entry2.js" + }, + output: { + filename: "./bundle2.js", } } ]; diff --git a/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/test.config.js b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/test.config.js new file mode 100644 index 00000000000..5b8eb45c9b9 --- /dev/null +++ b/packages/rspack-test-tools/tests/watchCases/compiler/multiply-compiler/test.config.js @@ -0,0 +1,10 @@ +module.exports = { + findBundle(i) { + switch (i) { + case 0: + return `bundle1.js` + case 1: + return `bundle2.js` + } + } +} diff --git a/packages/rspack-test-tools/tests/watchCases/context/out-of-context/test.config.js b/packages/rspack-test-tools/tests/watchCases/context/out-of-context/test.config.js index 933fb49f389..80fd7de43b6 100644 --- a/packages/rspack-test-tools/tests/watchCases/context/out-of-context/test.config.js +++ b/packages/rspack-test-tools/tests/watchCases/context/out-of-context/test.config.js @@ -1,6 +1,6 @@ module.exports = { documentType: 'fake', - findBundle() { - return ['bundle.css', 'bundle.js'] + findBundle(i) { + return ["bundle.css", "bundle.js"] } } diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/foo.js similarity index 100% rename from packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/foo.js rename to packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/foo.js diff --git a/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/index.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/index.js new file mode 100644 index 00000000000..978b35bc46f --- /dev/null +++ b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/index.js @@ -0,0 +1,5 @@ +import {value} from "./module"; + +it("should have correct export from re-exports", function () { + expect(value).toBe("foo"); +}); diff --git a/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/module.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/module.js new file mode 100644 index 00000000000..c53b4827bb0 --- /dev/null +++ b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/module.js @@ -0,0 +1,3 @@ +import {v} from './reexports' +// ... +export const value = '' + v; diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/package.json similarity index 100% rename from packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/package.json rename to packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/package.json diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/reexports-deep.js similarity index 100% rename from packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports-deep.js rename to packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/reexports-deep.js diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/reexports.js similarity index 100% rename from packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/reexports.js rename to packages/rspack-test-tools/tests/watchCases/side-effects/basic/0/reexports.js diff --git a/packages/rspack-test-tools/tests/watchCases/side-effects/basic/1/module.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/1/module.js new file mode 100644 index 00000000000..a5970107246 --- /dev/null +++ b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/1/module.js @@ -0,0 +1,2 @@ +import {v} from './reexports' +export const value = '' + v; diff --git a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/rspack.config.js similarity index 67% rename from packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js rename to packages/rspack-test-tools/tests/watchCases/side-effects/basic/rspack.config.js index 93f66ea6acc..f75570bd83a 100644 --- a/packages/rspack-test-tools/tests/hotCases/newTreeshaking/re-export-optimization-for-inaffected-module/rspack.config.js +++ b/packages/rspack-test-tools/tests/watchCases/side-effects/basic/rspack.config.js @@ -3,10 +3,5 @@ module.exports = { optimization: { sideEffects: true, providedExports: true, - }, - experiments: { - rspackFuture: { - newIncremental: true - } } }; From 9dff54434ce27706a4b62260018740ac458ba1e9 Mon Sep 17 00:00:00 2001 From: jinrui Date: Sat, 14 Sep 2024 11:44:54 +0800 Subject: [PATCH 50/51] feat: allow ModuleFactoryCreateData modify dependencies (#7895) --- .../node_binding/src/plugins/interceptor.rs | 11 +++--- .../src/compiler/make/repair/add.rs | 13 ++++--- .../src/compiler/make/repair/factorize.rs | 36 ++++++++++--------- .../src/compiler/make/repair/mod.rs | 3 +- .../make/repair/process_dependencies.rs | 8 ++--- .../src/compiler/module_executor/entry.rs | 4 +-- .../src/compiler/module_executor/overwrite.rs | 12 ++----- .../rspack_core/src/context_module_factory.rs | 3 +- crates/rspack_core/src/module_factory.rs | 11 +++--- .../rspack_core/src/normal_module_factory.rs | 7 ++-- crates/rspack_plugin_externals/src/plugin.rs | 3 +- .../src/css_module.rs | 3 +- .../src/dependency.rs | 3 +- .../src/factory.rs | 5 ++- .../src/plugin.rs | 4 +-- .../container_entry_module_factory.rs | 3 +- .../container/container_reference_plugin.rs | 3 +- .../src/container/fallback_module_factory.rs | 3 +- .../src/sharing/consume_shared_plugin.rs | 5 ++- .../sharing/provide_shared_module_factory.rs | 3 +- 20 files changed, 62 insertions(+), 81 deletions(-) diff --git a/crates/node_binding/src/plugins/interceptor.rs b/crates/node_binding/src/plugins/interceptor.rs index eca2912b79b..5b84aef9d6d 100644 --- a/crates/node_binding/src/plugins/interceptor.rs +++ b/crates/node_binding/src/plugins/interceptor.rs @@ -1267,8 +1267,7 @@ impl CompilationAfterSeal for CompilationAfterSealTap { #[async_trait] impl NormalModuleFactoryBeforeResolve for NormalModuleFactoryBeforeResolveTap { async fn run(&self, data: &mut ModuleFactoryCreateData) -> rspack_error::Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency_mut() .expect("should be module dependency"); match self @@ -1304,8 +1303,7 @@ impl NormalModuleFactoryFactorize for NormalModuleFactoryFactorizeTap { &self, data: &mut ModuleFactoryCreateData, ) -> rspack_error::Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency_mut() .expect("should be module dependency"); match self @@ -1342,8 +1340,7 @@ impl NormalModuleFactoryResolve for NormalModuleFactoryResolveTap { &self, data: &mut ModuleFactoryCreateData, ) -> rspack_error::Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency_mut() .expect("should be module dependency"); match self @@ -1486,7 +1483,7 @@ impl NormalModuleFactoryCreateModule for NormalModuleFactoryCreateModuleTap { self .function .call_with_promise(JsNormalModuleFactoryCreateModuleArgs { - dependency_type: data.dependency.dependency_type().to_string(), + dependency_type: data.dependencies[0].dependency_type().to_string(), raw_request: create_data.raw_request.clone(), resource_resolve_data: create_data.resource_resolve_data.clone().into(), context: data.context.to_string(), diff --git a/crates/rspack_core/src/compiler/make/repair/add.rs b/crates/rspack_core/src/compiler/make/repair/add.rs index c3dc32a8db7..db2d2d90a8c 100644 --- a/crates/rspack_core/src/compiler/make/repair/add.rs +++ b/crates/rspack_core/src/compiler/make/repair/add.rs @@ -4,7 +4,7 @@ use super::{build::BuildTask, MakeTaskContext}; use crate::{ module_graph::{ModuleGraph, ModuleGraphModule}, utils::task_loop::{Task, TaskResult, TaskType}, - DependencyId, Module, ModuleIdentifier, ModuleProfile, + BoxDependency, Module, ModuleIdentifier, ModuleProfile, }; #[derive(Debug)] @@ -12,7 +12,7 @@ pub struct AddTask { pub original_module_identifier: Option, pub module: Box, pub module_graph_module: Box, - pub dependencies: Vec, + pub dependencies: Vec, pub current_profile: Option>, } @@ -84,11 +84,16 @@ impl Task for AddTask { fn set_resolved_module( module_graph: &mut ModuleGraph, original_module_identifier: Option, - dependencies: Vec, + dependencies: Vec, module_identifier: ModuleIdentifier, ) -> Result<()> { for dependency in dependencies { - module_graph.set_resolved_module(original_module_identifier, dependency, module_identifier)?; + module_graph.set_resolved_module( + original_module_identifier, + *dependency.id(), + module_identifier, + )?; + module_graph.add_dependency(dependency); } Ok(()) } diff --git a/crates/rspack_core/src/compiler/make/repair/factorize.rs b/crates/rspack_core/src/compiler/make/repair/factorize.rs index f5252e1bdf7..a86e7ef11aa 100644 --- a/crates/rspack_core/src/compiler/make/repair/factorize.rs +++ b/crates/rspack_core/src/compiler/make/repair/factorize.rs @@ -8,9 +8,9 @@ use super::{add::AddTask, MakeTaskContext}; use crate::{ module_graph::ModuleGraphModule, utils::task_loop::{Task, TaskResult, TaskType}, - BoxDependency, CompilerOptions, Context, DependencyId, ExportInfoData, ExportsInfoData, - ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, ModuleLayer, - ModuleProfile, Resolve, + BoxDependency, CompilerOptions, Context, ExportInfoData, ExportsInfoData, ModuleFactory, + ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, ModuleLayer, ModuleProfile, + Resolve, }; #[derive(Debug)] @@ -21,8 +21,7 @@ pub struct FactorizeTask { pub original_module_context: Option>, pub issuer: Option>, pub issuer_layer: Option, - pub dependency: BoxDependency, - pub dependencies: Vec, + pub dependencies: Vec, pub resolve_options: Option>, pub options: Arc, pub current_profile: Option>, @@ -37,7 +36,7 @@ impl Task for FactorizeTask { if let Some(current_profile) = &self.current_profile { current_profile.mark_factory_start(); } - let dependency = self.dependency; + let dependency = &self.dependencies[0]; let context = if let Some(context) = dependency.get_context() && !context.is_empty() @@ -64,7 +63,7 @@ impl Task for FactorizeTask { // dependency: dep_id, original_module_identifier: self.original_module_identifier, factory_result: None, - dependencies: self.dependencies, + dependencies: vec![], current_profile: self.current_profile, exports_info_related: ExportsInfoRelated { exports_info, @@ -83,7 +82,7 @@ impl Task for FactorizeTask { resolve_options: self.resolve_options, options: self.options.clone(), context, - dependency, + dependencies: self.dependencies, issuer: self.issuer, issuer_identifier: self.original_module_identifier, issuer_layer, @@ -101,6 +100,7 @@ impl Task for FactorizeTask { let diagnostics = create_data.diagnostics.drain(..).collect(); Ok(vec![Box::new( factorize_result_task + .with_dependencies(create_data.dependencies) .with_factory_result(Some(result)) .with_diagnostics(diagnostics) .with_file_dependencies(create_data.file_dependencies.drain()) @@ -125,11 +125,12 @@ impl Task for FactorizeTask { return Err(e); } let mut diagnostics = Vec::with_capacity(create_data.diagnostics.len() + 1); - diagnostics.push(Into::::into(e).with_loc(create_data.dependency.loc())); + diagnostics.push(Into::::into(e).with_loc(create_data.dependencies[0].loc())); diagnostics.append(&mut create_data.diagnostics); // Continue bundling if `options.bail` set to `false`. Ok(vec![Box::new( factorize_result_task + .with_dependencies(create_data.dependencies) .with_diagnostics(diagnostics) .with_file_dependencies(create_data.file_dependencies.drain()) .with_missing_dependencies(create_data.missing_dependencies.drain()) @@ -154,7 +155,7 @@ pub struct FactorizeResultTask { pub original_module_identifier: Option, /// Result will be available if [crate::ModuleFactory::create] returns `Ok`. pub factory_result: Option, - pub dependencies: Vec, + pub dependencies: Vec, pub current_profile: Option>, pub exports_info_related: ExportsInfoRelated, @@ -165,6 +166,11 @@ pub struct FactorizeResultTask { } impl FactorizeResultTask { + fn with_dependencies(mut self, dependencies: Vec) -> Self { + self.dependencies = dependencies; + self + } + fn with_factory_result(mut self, factory_result: Option) -> Self { self.factory_result = factory_result; self @@ -214,7 +220,7 @@ impl Task for FactorizeResultTask { } else { artifact .make_failed_dependencies - .insert((dependencies[0], None)); + .insert((*dependencies[0].id(), None)); } } @@ -236,17 +242,13 @@ impl Task for FactorizeResultTask { let module_graph = &mut MakeTaskContext::get_module_graph_mut(&mut artifact.module_graph_partial); let Some(factory_result) = factory_result else { - let dep = module_graph - .dependency_by_id(&dependencies[0]) - .expect("dep should available"); + let dep = &dependencies[0]; tracing::trace!("Module created with failure, but without bailout: {dep:?}"); return Ok(vec![]); }; let Some(module) = factory_result.module else { - let dep = module_graph - .dependency_by_id(&dependencies[0]) - .expect("dep should available"); + let dep = &dependencies[0]; tracing::trace!("Module ignored: {dep:?}"); return Ok(vec![]); }; diff --git a/crates/rspack_core/src/compiler/make/repair/mod.rs b/crates/rspack_core/src/compiler/make/repair/mod.rs index d770c66153b..75f250fedee 100644 --- a/crates/rspack_core/src/compiler/make/repair/mod.rs +++ b/crates/rspack_core/src/compiler/make/repair/mod.rs @@ -128,8 +128,7 @@ pub fn repair( .and_then(|module| module.name_for_condition()), issuer_layer: parent_module.and_then(|m| m.get_layer()).cloned(), original_module_context: parent_module.and_then(|m| m.get_context()), - dependency: dependency.clone(), - dependencies: vec![id], + dependencies: vec![dependency.clone()], resolve_options: parent_module.and_then(|module| module.get_resolve_options()), options: compilation.options.clone(), current_profile, diff --git a/crates/rspack_core/src/compiler/make/repair/process_dependencies.rs b/crates/rspack_core/src/compiler/make/repair/process_dependencies.rs index defa8ff74a4..818633f3632 100644 --- a/crates/rspack_core/src/compiler/make/repair/process_dependencies.rs +++ b/crates/rspack_core/src/compiler/make/repair/process_dependencies.rs @@ -57,7 +57,7 @@ impl Task for ProcessDependenciesTask { sorted_dependencies .entry(resource_identifier) .or_insert(vec![]) - .push(dependency_id); + .push(dependency.clone()); } } @@ -71,10 +71,6 @@ impl Task for ProcessDependenciesTask { .compiler_options .profile .then(Box::::default); - let dependency = module_graph - .dependency_by_id(&dependencies[0]) - .expect("should have dependency") - .clone(); let original_module_source = module_graph .module_by_identifier(&original_module_identifier) .and_then(|m| m.as_normal_module()) @@ -85,6 +81,7 @@ impl Task for ProcessDependenciesTask { None } }); + let dependency = &dependencies[0]; let dependency_type = dependency.dependency_type(); // TODO move module_factory calculate to dependency factories let module_factory = context @@ -107,7 +104,6 @@ impl Task for ProcessDependenciesTask { .as_normal_module() .and_then(|module| module.name_for_condition()), issuer_layer: module.get_layer().cloned(), - dependency, dependencies, resolve_options: module.get_resolve_options(), options: context.compiler_options.clone(), diff --git a/crates/rspack_core/src/compiler/module_executor/entry.rs b/crates/rspack_core/src/compiler/module_executor/entry.rs index 53532678d02..29239ad78ac 100644 --- a/crates/rspack_core/src/compiler/module_executor/entry.rs +++ b/crates/rspack_core/src/compiler/module_executor/entry.rs @@ -40,7 +40,6 @@ impl Task for EntryTask { Ok(vec![]) } EntryParam::Entry(dep) => { - let dep_id = *dep.id(); module_graph.add_dependency(dep.clone()); Ok(vec![Box::new(FactorizeTask { module_factory: context @@ -58,8 +57,7 @@ impl Task for EntryTask { issuer: None, issuer_layer: None, original_module_context: None, - dependency: dep, - dependencies: vec![dep_id], + dependencies: vec![dep], resolve_options: None, options: context.compiler_options.clone(), current_profile: context diff --git a/crates/rspack_core/src/compiler/module_executor/overwrite.rs b/crates/rspack_core/src/compiler/module_executor/overwrite.rs index 924b79d00a2..362820e80e6 100644 --- a/crates/rspack_core/src/compiler/module_executor/overwrite.rs +++ b/crates/rspack_core/src/compiler/module_executor/overwrite.rs @@ -41,11 +41,7 @@ impl Task for OverwriteTask { // factorize result task if let Some(factorize_result_task) = origin_task.as_any().downcast_ref::() { - let dep_id = factorize_result_task - .dependencies - .first() - .copied() - .expect("should have dep_id"); + let dep_id = *factorize_result_task.dependencies[0].id(); let original_module_identifier = factorize_result_task.original_module_identifier; let res = origin_task.sync_run(context)?; if res.is_empty() { @@ -57,11 +53,7 @@ impl Task for OverwriteTask { } // add task if let Some(add_task) = origin_task.as_any().downcast_ref::() { - let dep_id = add_task - .dependencies - .first() - .copied() - .expect("should have dep_id"); + let dep_id = *add_task.dependencies[0].id(); let original_module_identifier = add_task.original_module_identifier; let target_module_identifier = add_task.module.identifier(); diff --git a/crates/rspack_core/src/context_module_factory.rs b/crates/rspack_core/src/context_module_factory.rs index c6a02db9377..128704ed912 100644 --- a/crates/rspack_core/src/context_module_factory.rs +++ b/crates/rspack_core/src/context_module_factory.rs @@ -154,8 +154,7 @@ impl ContextModuleFactory { data: &mut ModuleFactoryCreateData, ) -> Result<(ModuleFactoryResult, Option)> { let plugin_driver = &self.plugin_driver; - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_context_dependency() .expect("should be context dependency"); let mut file_dependencies = Default::default(); diff --git a/crates/rspack_core/src/module_factory.rs b/crates/rspack_core/src/module_factory.rs index 4a6450995c4..c078390506e 100644 --- a/crates/rspack_core/src/module_factory.rs +++ b/crates/rspack_core/src/module_factory.rs @@ -12,7 +12,7 @@ pub struct ModuleFactoryCreateData { pub resolve_options: Option>, pub options: Arc, pub context: Context, - pub dependency: BoxDependency, + pub dependencies: Vec, pub issuer: Option>, pub issuer_identifier: Option, pub issuer_layer: Option, @@ -25,11 +25,14 @@ pub struct ModuleFactoryCreateData { impl ModuleFactoryCreateData { pub fn request(&self) -> Option<&str> { - self - .dependency + self.dependencies[0] .as_module_dependency() .map(|d| d.request()) - .or_else(|| self.dependency.as_context_dependency().map(|d| d.request())) + .or_else(|| { + self.dependencies[0] + .as_context_dependency() + .map(|d| d.request()) + }) } pub fn add_file_dependency(&mut self, file: PathBuf) { diff --git a/crates/rspack_core/src/normal_module_factory.rs b/crates/rspack_core/src/normal_module_factory.rs index 4caff05fadc..3e614300c48 100644 --- a/crates/rspack_core/src/normal_module_factory.rs +++ b/crates/rspack_core/src/normal_module_factory.rs @@ -136,8 +136,7 @@ impl NormalModuleFactory { &self, data: &mut ModuleFactoryCreateData, ) -> Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency() .expect("should be module dependency"); let dependency_type = *dependency.dependency_type(); @@ -216,7 +215,7 @@ impl NormalModuleFactory { { Some((pos, _)) => &request_without_match_resource[pos..], None => { - unreachable!("Invalid dependency: {:?}", &data.dependency) + unreachable!("Invalid dependency: {:?}", &data.dependencies[0]) } } } else { @@ -389,7 +388,7 @@ impl NormalModuleFactory { } else { &resource_data }, - data.dependency.as_ref(), + data.dependencies[0].as_ref(), data.issuer.as_deref(), data.issuer_layer.as_deref(), ) diff --git a/crates/rspack_plugin_externals/src/plugin.rs b/crates/rspack_plugin_externals/src/plugin.rs index de4b709ae08..210002ac4d7 100644 --- a/crates/rspack_plugin_externals/src/plugin.rs +++ b/crates/rspack_plugin_externals/src/plugin.rs @@ -133,8 +133,7 @@ impl ExternalsPlugin { #[plugin_hook(NormalModuleFactoryFactorize for ExternalsPlugin)] async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency() .expect("should be module dependency"); let context = &data.context; diff --git a/crates/rspack_plugin_extract_css/src/css_module.rs b/crates/rspack_plugin_extract_css/src/css_module.rs index 688463694b4..79cb53b7449 100644 --- a/crates/rspack_plugin_extract_css/src/css_module.rs +++ b/crates/rspack_plugin_extract_css/src/css_module.rs @@ -241,8 +241,7 @@ pub(crate) struct CssModuleFactory; #[async_trait::async_trait] impl ModuleFactory for CssModuleFactory { async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let css_dep = data - .dependency + let css_dep = data.dependencies[0] .downcast_ref::() .expect("unreachable"); diff --git a/crates/rspack_plugin_lazy_compilation/src/dependency.rs b/crates/rspack_plugin_lazy_compilation/src/dependency.rs index c0f26f2dc18..f6e46e61490 100644 --- a/crates/rspack_plugin_lazy_compilation/src/dependency.rs +++ b/crates/rspack_plugin_lazy_compilation/src/dependency.rs @@ -12,8 +12,7 @@ pub(crate) struct LazyCompilationDependency { impl LazyCompilationDependency { pub fn new(original_module_create_data: ModuleFactoryCreateData) -> Self { - let dep = original_module_create_data - .dependency + let dep = original_module_create_data.dependencies[0] .as_module_dependency() .expect("LazyCompilation: should convert to module dependency"); let request = dep.request().to_string(); diff --git a/crates/rspack_plugin_lazy_compilation/src/factory.rs b/crates/rspack_plugin_lazy_compilation/src/factory.rs index ae6ed1f35db..7b07381da70 100644 --- a/crates/rspack_plugin_lazy_compilation/src/factory.rs +++ b/crates/rspack_plugin_lazy_compilation/src/factory.rs @@ -23,8 +23,7 @@ impl LazyCompilationDependencyFactory { #[async_trait::async_trait] impl ModuleFactory for LazyCompilationDependencyFactory { async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let dep: &LazyCompilationDependency = data - .dependency + let dep: &LazyCompilationDependency = data.dependencies[0] .as_any() .downcast_ref() .expect("should be lazy compile dependency"); @@ -37,7 +36,7 @@ impl ModuleFactory for LazyCompilationDependencyFactory { resolve_options: proxy_data.resolve_options.clone(), options: data.options.clone(), context: proxy_data.context.clone(), - dependency: Box::new(dep), + dependencies: vec![Box::new(dep)], issuer: proxy_data.issuer.clone(), issuer_layer: proxy_data.issuer_layer.clone(), issuer_identifier: proxy_data.issuer_identifier, diff --git a/crates/rspack_plugin_lazy_compilation/src/plugin.rs b/crates/rspack_plugin_lazy_compilation/src/plugin.rs index 1cb836fc08a..84096bb32d6 100644 --- a/crates/rspack_plugin_lazy_compilation/src/plugin.rs +++ b/crates/rspack_plugin_lazy_compilation/src/plugin.rs @@ -96,7 +96,7 @@ async fn normal_module_factory_module( create_data: &mut NormalModuleCreateData, module: &mut BoxModule, ) -> Result<()> { - let dep_type = module_factory_create_data.dependency.dependency_type(); + let dep_type = module_factory_create_data.dependencies[0].dependency_type(); if matches!(dep_type, DependencyType::LazyImport) { return Ok(()); @@ -134,7 +134,7 @@ async fn normal_module_factory_module( } // ignore global entry - let entry: Option<&EntryDependency> = module_factory_create_data.dependency.downcast_ref(); + let entry: Option<&EntryDependency> = module_factory_create_data.dependencies[0].downcast_ref(); let Some(entry) = entry else { return Ok(()); }; diff --git a/crates/rspack_plugin_mf/src/container/container_entry_module_factory.rs b/crates/rspack_plugin_mf/src/container/container_entry_module_factory.rs index d766d5c7342..9cf1ba1db57 100644 --- a/crates/rspack_plugin_mf/src/container/container_entry_module_factory.rs +++ b/crates/rspack_plugin_mf/src/container/container_entry_module_factory.rs @@ -13,8 +13,7 @@ pub struct ContainerEntryModuleFactory; #[async_trait] impl ModuleFactory for ContainerEntryModuleFactory { async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let dep = data - .dependency + let dep = data.dependencies[0] .downcast_ref::() .expect("dependency of ContainerEntryModuleFactory should be ContainerEntryDependency"); Ok(ModuleFactoryResult::new_with_module(Box::new( diff --git a/crates/rspack_plugin_mf/src/container/container_reference_plugin.rs b/crates/rspack_plugin_mf/src/container/container_reference_plugin.rs index 4343e718ed8..ff65bc977a8 100644 --- a/crates/rspack_plugin_mf/src/container/container_reference_plugin.rs +++ b/crates/rspack_plugin_mf/src/container/container_reference_plugin.rs @@ -65,8 +65,7 @@ async fn compilation( #[plugin_hook(NormalModuleFactoryFactorize for ContainerReferencePlugin)] async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { - let dependency = data - .dependency + let dependency = data.dependencies[0] .as_module_dependency() .expect("should be module dependency"); let request = dependency.request(); diff --git a/crates/rspack_plugin_mf/src/container/fallback_module_factory.rs b/crates/rspack_plugin_mf/src/container/fallback_module_factory.rs index 26716e222a5..f0c7c0ee5f0 100644 --- a/crates/rspack_plugin_mf/src/container/fallback_module_factory.rs +++ b/crates/rspack_plugin_mf/src/container/fallback_module_factory.rs @@ -10,8 +10,7 @@ pub struct FallbackModuleFactory; #[async_trait] impl ModuleFactory for FallbackModuleFactory { async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let dep = data - .dependency + let dep = data.dependencies[0] .downcast_ref::() .expect("dependency of FallbackModuleFactory should be FallbackDependency"); Ok(ModuleFactoryResult::new_with_module(Box::new( diff --git a/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs b/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs index 642a592a7d8..832c8eb1216 100644 --- a/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs +++ b/crates/rspack_plugin_mf/src/sharing/consume_shared_plugin.rs @@ -332,8 +332,7 @@ async fn this_compilation( #[plugin_hook(NormalModuleFactoryFactorize for ConsumeSharedPlugin)] async fn factorize(&self, data: &mut ModuleFactoryCreateData) -> Result> { - let dep = data - .dependency + let dep = data.dependencies[0] .as_module_dependency() .expect("should be module dependency"); if matches!( @@ -386,7 +385,7 @@ async fn create_module( create_data: &mut NormalModuleCreateData, ) -> Result> { if matches!( - data.dependency.dependency_type(), + data.dependencies[0].dependency_type(), DependencyType::ConsumeSharedFallback | DependencyType::ProvideModuleForShared ) { return Ok(None); diff --git a/crates/rspack_plugin_mf/src/sharing/provide_shared_module_factory.rs b/crates/rspack_plugin_mf/src/sharing/provide_shared_module_factory.rs index 49166cc0d47..8829c643750 100644 --- a/crates/rspack_plugin_mf/src/sharing/provide_shared_module_factory.rs +++ b/crates/rspack_plugin_mf/src/sharing/provide_shared_module_factory.rs @@ -16,8 +16,7 @@ pub struct ProvideSharedModuleFactory { #[async_trait] impl ModuleFactory for ProvideSharedModuleFactory { async fn create(&self, data: &mut ModuleFactoryCreateData) -> Result { - let dep = data - .dependency + let dep = data.dependencies[0] .downcast_ref::() .expect("dependency of ProvideSharedModuleFactory should be ProvideSharedDependency"); Ok(ModuleFactoryResult::new_with_module(Box::new( From 3fb8f24c1d39930cef394795a4578e0765bc8b76 Mon Sep 17 00:00:00 2001 From: ZhangMingyuan <71547074+zhangmingyuan666@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:55:33 +0800 Subject: [PATCH 51/51] docs: update how to use swc plugin in rspack rule (#7904) --- .../zh/guide/features/builtin-swc-loader.mdx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/website/docs/zh/guide/features/builtin-swc-loader.mdx b/website/docs/zh/guide/features/builtin-swc-loader.mdx index 8d5ec9c1e6a..571c43d85a8 100644 --- a/website/docs/zh/guide/features/builtin-swc-loader.mdx +++ b/website/docs/zh/guide/features/builtin-swc-loader.mdx @@ -139,15 +139,17 @@ Rspack 支持在 `builtin:swc-loader` 里加载 SWC 的 Wasm 插件, 你可以 use: { loader: 'builtin:swc-loader', options: { - experimental: { - plugins: [ - [ - '@swc/plugin-remove-console', - { - exclude: ['error'], - }, + jsc: { + experimental: { + plugins: [ + [ + '@swc/plugin-remove-console', + { + exclude: ['error'], + }, + ], ], - ], + }, }, }, },