diff --git a/crates/rspack_core/src/context_module.rs b/crates/rspack_core/src/context_module.rs index 207a0a8c038..a17bdde0b36 100644 --- a/crates/rspack_core/src/context_module.rs +++ b/crates/rspack_core/src/context_module.rs @@ -129,8 +129,10 @@ pub struct ContextOptions { pub recursive: bool, #[derivative(Hash = "ignore", PartialEq = "ignore")] pub reg_exp: Option, - pub include: Option, - pub exclude: Option, + #[derivative(Hash = "ignore", PartialEq = "ignore")] + pub include: Option, + #[derivative(Hash = "ignore", PartialEq = "ignore")] + pub exclude: Option, pub category: DependencyCategory, pub request: String, pub context: String, @@ -1000,8 +1002,19 @@ impl ContextModule { if !dir.is_dir() { return Ok(()); } + let include = &options.context_options.include; + let exclude = &options.context_options.exclude; for entry in fs::read_dir(dir).into_diagnostic()? { let path = entry.into_diagnostic()?.path(); + let path_str = path.to_string_lossy().to_string(); + + if let Some(exclude) = exclude + && exclude.test(&path_str) + { + // ignore excluded files + continue; + } + if path.is_dir() { if options.context_options.recursive { Self::visit_dirs(ctx, &path, dependencies, options, resolve_options)?; @@ -1013,11 +1026,17 @@ impl ContextModule { // ignore hidden files continue; } else { + if let Some(include) = include + && !include.test(&path_str) + { + // ignore not included files + continue; + } + // FIXME: nodejs resolver return path of context, sometimes is '/a/b', sometimes is '/a/b/' let relative_path = { - let p = path - .to_string_lossy() - .to_string() + let p = path_str + .clone() .drain(ctx.len()..) .collect::() .replace('\\', "/"); @@ -1027,6 +1046,7 @@ impl ContextModule { format!("./{p}") } }; + let requests = alternative_requests( resolve_options, vec![AlternativeRequest::new(ctx.to_string(), relative_path)], @@ -1191,11 +1211,11 @@ fn create_identifier(options: &ContextModuleOptions) -> Identifier { } if let Some(include) = &options.context_options.include { id += "|include: "; - id += &include; + id += &include.to_source_string(); } if let Some(exclude) = &options.context_options.exclude { id += "|exclude: "; - id += &exclude; + id += &exclude.to_source_string(); } if let Some(GroupOptions::ChunkGroup(group)) = &options.context_options.group_options { if let Some(chunk_name) = &group.name { diff --git a/crates/rspack_plugin_javascript/src/dependency/context/mod.rs b/crates/rspack_plugin_javascript/src/dependency/context/mod.rs index f9fabd252fe..c9828b39376 100644 --- a/crates/rspack_plugin_javascript/src/dependency/context/mod.rs +++ b/crates/rspack_plugin_javascript/src/dependency/context/mod.rs @@ -23,8 +23,16 @@ fn create_resource_identifier_for_context_dependency( .as_ref() .map(|r| r.to_source_string()) .unwrap_or_default(); - let include = options.include.as_deref().unwrap_or_default(); - let exclude = options.exclude.as_deref().unwrap_or_default(); + let include = options + .include + .as_ref() + .map(|x| x.to_source_string()) + .unwrap_or_default(); + let exclude = options + .exclude + .as_ref() + .map(|x| x.to_source_string()) + .unwrap_or_default(); let mode = options.mode.as_str(); // TODO: need `RawChunkGroupOptions` let id = format!( diff --git a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs index d31dd8dd1b0..26bcd00dc4b 100644 --- a/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs +++ b/crates/rspack_plugin_javascript/src/parser_plugin/import_parser_plugin.rs @@ -68,6 +68,8 @@ impl JavascriptParserPlugin for ImportParserPlugin { .get_fetch_priority() .map(|x| DynamicImportFetchPriority::from(x.as_str())) .or(dynamic_import_fetch_priority); + let include = magic_comment_options.get_webpack_include(); + let exclude = magic_comment_options.get_webpack_exclude(); let param = parser.evaluate_expression(dyn_imported.expr.as_ref()); @@ -131,8 +133,8 @@ impl JavascriptParserPlugin for ImportParserPlugin { mode: mode.into(), recursive: true, reg_exp, - include: None, - exclude: None, + include, + exclude, category: DependencyCategory::Esm, request: format!("{}{}{}", context.clone(), query, fragment), context, diff --git a/crates/rspack_plugin_javascript/src/webpack_comment.rs b/crates/rspack_plugin_javascript/src/webpack_comment.rs index 928ff899842..1759801eb51 100644 --- a/crates/rspack_plugin_javascript/src/webpack_comment.rs +++ b/crates/rspack_plugin_javascript/src/webpack_comment.rs @@ -1,6 +1,7 @@ use once_cell::sync::Lazy; use regex::Captures; use rspack_error::miette::{Diagnostic, Severity}; +use rspack_regex::RspackRegex; use rustc_hash::{FxHashMap, FxHashSet}; use swc_core::common::comments::{Comment, CommentKind, Comments}; use swc_core::common::{SourceFile, Span}; @@ -14,6 +15,10 @@ pub enum WebpackComment { Preload, Ignore, FetchPriority, + IncludeRegexp, + IncludeFlags, + ExcludeRegexp, + ExcludeFlags, Mode, } @@ -60,6 +65,36 @@ impl WebpackCommentMap { pub fn get_fetch_priority(&self) -> Option<&String> { self.0.get(&WebpackComment::FetchPriority) } + + pub fn get_webpack_include(&self) -> Option { + self.0.get(&WebpackComment::IncludeRegexp).map(|expr| { + let flags = self + .0 + .get(&WebpackComment::IncludeFlags) + .map(|x| x.as_str()) + .unwrap_or_default(); + + RspackRegex::with_flags(expr, flags).unwrap_or_else(|_| { + // test when capture + unreachable!(); + }) + }) + } + + pub fn get_webpack_exclude(&self) -> Option { + self.0.get(&WebpackComment::ExcludeRegexp).map(|expr| { + let flags = self + .0 + .get(&WebpackComment::ExcludeFlags) + .map(|x| x.as_str()) + .unwrap_or_default(); + + RspackRegex::with_flags(expr, flags).unwrap_or_else(|_| { + // test when capture + unreachable!(); + }) + }) + } } fn add_magic_comment_warning( @@ -91,9 +126,10 @@ fn add_magic_comment_warning( // _3 for `xxx` // _4 for number // _5 for true/false +// _6 for regexp // TODO: regexp/array static WEBPACK_MAGIC_COMMENT_REGEXP: Lazy = Lazy::new(|| { - regex::Regex::new(r#"(?P<_0>webpack[a-zA-Z\d_-]+)\s*:\s*("(?P<_1>[^"]+)"|'(?P<_2>[^']+)'|`(?P<_3>[^`]+)`|(?P<_4>[\d.-]+)|(?P<_5>true|false))"#) + regex::Regex::new(r#"(?P<_0>webpack[a-zA-Z\d_-]+)\s*:\s*("(?P<_1>[^"]+)"|'(?P<_2>[^']+)'|`(?P<_3>[^`]+)`|(?P<_4>[\d.-]+)|(?P<_5>true|false)|(?P<_6>/([^,]+)/([dgimsuvy]*)))"#) .expect("invalid regex") }); @@ -260,6 +296,48 @@ fn analyze_comments( } } } + "webpackInclude" => { + if captures.name("_6").is_some() { + if let Some(regexp) = captures.get(9).map(|x| x.as_str()) { + let flags = captures.get(10).map(|x| x.as_str()).unwrap_or_default(); + if RspackRegex::with_flags(regexp, flags).is_ok() { + result.insert(WebpackComment::IncludeRegexp, regexp.to_string()); + result.insert(WebpackComment::IncludeFlags, flags.to_string()); + return; + } else { + add_magic_comment_warning( + source_file, + item_name, + r#"a regular expression"#, + &captures, + warning_diagnostics, + error_span, + ); + } + } + } + } + "webpackExclude" => { + if captures.name("_6").is_some() { + if let Some(regexp) = captures.get(9).map(|x| x.as_str()) { + let flags = captures.get(10).map(|x| x.as_str()).unwrap_or_default(); + if RspackRegex::with_flags(regexp, flags).is_ok() { + result.insert(WebpackComment::ExcludeRegexp, regexp.to_string()); + result.insert(WebpackComment::ExcludeFlags, flags.to_string()); + return; + } else { + add_magic_comment_warning( + source_file, + item_name, + r#"a regular expression"#, + &captures, + warning_diagnostics, + error_span, + ); + } + } + } + } _ => { // TODO: other magic comment } diff --git a/tests/webpack-test/__snapshots__/StatsTestCases.basictest.js.snap b/tests/webpack-test/__snapshots__/StatsTestCases.basictest.js.snap index e59317f573f..65134b0c739 100644 --- a/tests/webpack-test/__snapshots__/StatsTestCases.basictest.js.snap +++ b/tests/webpack-test/__snapshots__/StatsTestCases.basictest.js.snap @@ -1,5 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`StatsTestCases should print correct stats for import-context-filter 1`] = ` +"asset entry.js 9.65 KiB [emitted] (name: entry) +asset 228.js 426 bytes [emitted] +asset 271.js 426 bytes [emitted] +asset 536.js 426 bytes [emitted] +Entrypoint entry 9.65 KiB = entry.js +runtime modules 6.94 KiB 9 modules +cacheable modules 724 bytes + ./entry.js 450 bytes [built] [code generated] + Xdir/import-context-filter/templates|lazy|/^\\\\.\\\\/.*$/|exclude: /\\\\.noimport\\\\.js$/|groupOptions: {}|namespace object 160 bytes [built] [code generated] + ./templates/bar.js 38 bytes [built] [code generated] + ./templates/baz.js 38 bytes [built] [code generated] + ./templates/foo.js 38 bytes [built] [code generated] +Rspack x.x.x compiled successfully in X s" +`; + exports[`StatsTestCases should print correct stats for performance-different-mode-and-target 1`] = ` "asset warning.pro-web.js 294 KiB [emitted] (name: main) Entrypoint main 294 KiB = warning.pro-web.js diff --git a/tests/webpack-test/statsCases/import-context-filter/test.filter.js b/tests/webpack-test/statsCases/import-context-filter/test.filter.js deleted file mode 100644 index 7ba279ea1ea..00000000000 --- a/tests/webpack-test/statsCases/import-context-filter/test.filter.js +++ /dev/null @@ -1,3 +0,0 @@ - -module.exports = () => {return false} - \ No newline at end of file diff --git a/website/docs/en/api/modules/module-methods.mdx b/website/docs/en/api/modules/module-methods.mdx index e94098c7989..3c43a2410bf 100644 --- a/website/docs/en/api/modules/module-methods.mdx +++ b/website/docs/en/api/modules/module-methods.mdx @@ -103,15 +103,24 @@ import(`./locale/${language}.json`).then(module => { Inline comments to make features work. By adding comments to the import, we can do things such as name our chunk or select different modes. For a full list of these magic comments see the code below followed by an explanation of what these comments do. ```js +// Single target import( /* webpackChunkName: "my-chunk-name" */ - /* webpackPrefetch: true */ - /* webpackPreload: true */ /* webpackMode: "lazy" */ - /* webpackIgnore: true */ /* webpackFetchPriority: "high" */ 'module' ); + +// Multiple possible targets +import( + /* webpackInclude: /\.json$/ */ + /* webpackExclude: /\.noimport\.json$/ */ + /* webpackChunkName: "my-chunk-name" */ + /* webpackMode: "lazy" */ + /* webpackPrefetch: true */ + /* webpackPreload: true */ + `./locale/${language}` +); ``` ##### webpackIgnore @@ -171,6 +180,26 @@ A name for the new chunk. Set [`fetchPriority`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) for specific dynamic imports. It's also possible to set a global default value for all dynamic imports by using the `module.parser.javascript.dynamicImportFetchPriority` option. +##### webpackInclude + + + +- **Type:**: `Regexp` + +A regular expression that will be matched against during import resolution. Only modules that match **will be bundled**. + +##### webpackExclude + + + +- **Type:**: `Regexp` + +A regular expression that will be matched against during import resolution. Any module that matches **will not be bundled**. + +:::info +Note that `webpackInclude` and `webpackExclude` options do not interfere with the prefix. eg: `./locale`. +::: + ## CommonJS Rspack is also support `CommonJS` syntax natively, you can use `require` and `module.exports` methods. diff --git a/website/docs/zh/api/modules/module-methods.mdx b/website/docs/zh/api/modules/module-methods.mdx index db742a12245..4123736a91f 100644 --- a/website/docs/zh/api/modules/module-methods.mdx +++ b/website/docs/zh/api/modules/module-methods.mdx @@ -101,15 +101,24 @@ import(`./locale/${language}.json`).then(module => { 通过向 import 语句添加注释,我们可以执行诸如命名 chunk 或选择不同模式等操作。有关这些魔法注释的完整列表,请参见下面的代码,以及对这些注释功能的解释。 ```js +// 单个模块 import( /* webpackChunkName: "my-chunk-name" */ - /* webpackPrefetch: true */ - /* webpackPreload: true */ /* webpackMode: "lazy" */ - /* webpackIgnore: true */ /* webpackFetchPriority: "high" */ 'module' ); + +// 多个可能模块 +import( + /* webpackInclude: /\.json$/ */ + /* webpackExclude: /\.noimport\.json$/ */ + /* webpackChunkName: "my-chunk-name" */ + /* webpackMode: "lazy" */ + /* webpackPrefetch: true */ + /* webpackPreload: true */ + `./locale/${language}` +); ``` ##### webpackIgnore @@ -169,6 +178,26 @@ import( 为指定的动态导入设置 [`fetchPriority`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority)。也可以通过使用 `module.parser.javascript.dynamicImportFetchPriority` 选项为所有动态导入设置全局默认值。 +##### webpackInclude + + + +- **类型:**: `Regexp` + +在导入解析时匹配的正则表达式。只有匹配的模块**才会被打包**。 + +##### webpackExclude + + + +- **类型:**: `Regexp` + +在导入解析时匹配的正则表达式。只有匹配的模块**不会被打包**。 + +:::info +请注意,`webpackInclude` 和 `webpackExclude` 选项不会影响前缀。例如:`./locale`。 +::: + ## CommonJS Rspack 也支持 `CommonJS` 语法,可以使用 `require` 和 `module.exports` 语法。 diff --git a/website/project-words.txt b/website/project-words.txt index 62fe28df919..a603d062f5d 100644 --- a/website/project-words.txt +++ b/website/project-words.txt @@ -93,6 +93,7 @@ NAPI-RS nativizing nestjs NestJS +noimport nosources nwjs optionsrspackexperiments @@ -162,3 +163,4 @@ xxhash Zack Zack Jackson zackarychapple +