diff --git a/.changeset/moody-needles-train.md b/.changeset/moody-needles-train.md new file mode 100644 index 000000000000..d4563df586e8 --- /dev/null +++ b/.changeset/moody-needles-train.md @@ -0,0 +1,6 @@ +--- +"@rspack/binding": patch +"@rspack/core": patch +--- + +feat: module.rule[].dependency diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 27188f86ab8e..91dde267c1b2 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -227,6 +227,7 @@ export interface RawModuleRule { generator?: RawModuleRuleGenerator resolve?: RawResolveOptions issuer?: RawRuleSetCondition + dependency?: RawRuleSetCondition oneOf?: Array } export interface RawModuleRuleGenerator { diff --git a/crates/rspack_binding_options/src/options/raw_module.rs b/crates/rspack_binding_options/src/options/raw_module.rs index d79a470bf25b..cbf5316b7c60 100644 --- a/crates/rspack_binding_options/src/options/raw_module.rs +++ b/crates/rspack_binding_options/src/options/raw_module.rs @@ -230,6 +230,7 @@ pub struct RawModuleRule { pub generator: Option, pub resolve: Option, pub issuer: Option, + pub dependency: Option, pub one_of: Option>, } @@ -589,6 +590,7 @@ impl TryFrom for ModuleRule { resolve: value.resolve.map(|raw| raw.try_into()).transpose()?, side_effects: value.side_effects, issuer: value.issuer.map(|raw| raw.try_into()).transpose()?, + dependency: value.dependency.map(|raw| raw.try_into()).transpose()?, one_of, }) } diff --git a/crates/rspack_core/src/dependency/mod.rs b/crates/rspack_core/src/dependency/mod.rs index 36ae351568b8..911b663a85d8 100644 --- a/crates/rspack_core/src/dependency/mod.rs +++ b/crates/rspack_core/src/dependency/mod.rs @@ -11,7 +11,11 @@ pub use common_js_require_context_dependency::*; pub use const_dependency::ConstDependency; pub use import_context_dependency::*; mod require_context_dependency; -use std::{any::Any, fmt::Debug, hash::Hash}; +use std::{ + any::Any, + fmt::{Debug, Display}, + hash::Hash, +}; use dyn_clone::{clone_trait_object, DynClone}; pub use require_context_dependency::RequireContextDependency; @@ -95,6 +99,20 @@ impl From<&str> for DependencyCategory { } } +impl Display for DependencyCategory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DependencyCategory::Unknown => write!(f, "unknown"), + DependencyCategory::Esm => write!(f, "esm"), + DependencyCategory::CommonJS => write!(f, "commonjs"), + DependencyCategory::Url => write!(f, "url"), + DependencyCategory::CssImport => write!(f, "css-import"), + DependencyCategory::CssCompose => write!(f, "css-compose"), + DependencyCategory::Wasm => write!(f, "wasm"), + } + } +} + pub trait Dependency: CodeGeneratable + AsAny + DynHash + DynClone + DynEq + Send + Sync + Debug { diff --git a/crates/rspack_core/src/normal_module_factory.rs b/crates/rspack_core/src/normal_module_factory.rs index a2062c42c434..1e2eb7b3cc3a 100644 --- a/crates/rspack_core/src/normal_module_factory.rs +++ b/crates/rspack_core/src/normal_module_factory.rs @@ -9,10 +9,10 @@ use swc_core::common::Span; use crate::{ cache::Cache, module_rule_matcher, resolve, AssetGeneratorOptions, AssetParserOptions, - CompilerOptions, Dependency, FactorizeArgs, MissingModule, ModuleArgs, ModuleDependency, - ModuleExt, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, ModuleIdentifier, - ModuleRule, ModuleType, NormalModule, NormalModuleFactoryResolveForSchemeArgs, RawModule, - Resolve, ResolveArgs, ResolveError, ResolveResult, ResourceData, SharedPluginDriver, + CompilerOptions, Dependency, DependencyCategory, FactorizeArgs, MissingModule, ModuleArgs, + ModuleDependency, ModuleExt, ModuleFactory, ModuleFactoryCreateData, ModuleFactoryResult, + ModuleIdentifier, ModuleRule, ModuleType, NormalModule, NormalModuleFactoryResolveForSchemeArgs, + RawModule, Resolve, ResolveArgs, ResolveError, ResolveResult, ResourceData, SharedPluginDriver, }; #[derive(Debug)] @@ -172,26 +172,11 @@ impl NormalModuleFactory { } }; //TODO: with contextScheme - let loaders = self - .context - .options - .module - .rules - .iter() - .filter_map(|module_rule| -> Option> { - match module_rule_matcher( - module_rule, - &resource_data, - importer.map(|i| i.to_string_lossy()).as_deref(), - ) { - Ok(val) => val.map(Ok), - Err(err) => Some(Err(err)), - } - }) - .collect::>>()?; + let resolved_module_rules = + self.calculate_module_rules(&resource_data, data.dependency.category())?; - let loaders = loaders - .into_iter() + let loaders = resolved_module_rules + .iter() .flat_map(|module_rule| module_rule.r#use.iter().cloned().rev()) .collect::>(); @@ -210,7 +195,6 @@ impl NormalModuleFactory { let file_dependency = resource_data.resource_path.clone(); - let resolved_module_rules = self.calculate_module_rules(&resource_data)?; let resolved_module_type = self.calculate_module_type(&resolved_module_rules, self.context.module_type); let resolved_resolve_options = self.calculate_resolve_options(&resolved_module_rules); @@ -270,7 +254,11 @@ impl NormalModuleFactory { )) } - fn calculate_module_rules(&self, resource_data: &ResourceData) -> Result> { + fn calculate_module_rules( + &self, + resource_data: &ResourceData, + dependency: &DependencyCategory, + ) -> Result> { self .context .options @@ -278,7 +266,12 @@ impl NormalModuleFactory { .rules .iter() .filter_map(|module_rule| -> Option> { - match module_rule_matcher(module_rule, resource_data, self.context.issuer.as_deref()) { + match module_rule_matcher( + module_rule, + resource_data, + self.context.issuer.as_deref(), + dependency, + ) { Ok(val) => val.map(Ok), Err(err) => Some(Err(err)), } diff --git a/crates/rspack_core/src/options/module.rs b/crates/rspack_core/src/options/module.rs index 19db428ae889..b62d3873747f 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -117,6 +117,7 @@ pub struct ModuleRule { /// A condition matcher against the resource query. pub resource_query: Option, pub side_effects: Option, + pub dependency: Option, /// The `ModuleType` to use for the matched resource. pub r#type: Option, pub r#use: Vec, diff --git a/crates/rspack_core/src/utils/module_rules.rs b/crates/rspack_core/src/utils/module_rules.rs index 1d1b4e4ff623..ca7246f5ed4c 100644 --- a/crates/rspack_core/src/utils/module_rules.rs +++ b/crates/rspack_core/src/utils/module_rules.rs @@ -1,13 +1,14 @@ use rspack_error::{internal_error, Result}; use rspack_loader_runner::ResourceData; -use crate::ModuleRule; +use crate::{DependencyCategory, ModuleRule}; /// Match the `ModuleRule` against the given `ResourceData`, and return the matching `ModuleRule` if matched. pub fn module_rule_matcher<'a>( module_rule: &'a ModuleRule, resource_data: &ResourceData, issuer: Option<&str>, + dependency: &DependencyCategory, ) -> Result> { if module_rule.test.is_none() && module_rule.resource.is_none() @@ -15,6 +16,7 @@ pub fn module_rule_matcher<'a>( && module_rule.include.is_none() && module_rule.exclude.is_none() && module_rule.issuer.is_none() + && module_rule.dependency.is_none() && module_rule.one_of.is_none() { return Err(internal_error!( @@ -22,13 +24,14 @@ pub fn module_rule_matcher<'a>( )); } - module_rule_matcher_inner(module_rule, resource_data, issuer) + module_rule_matcher_inner(module_rule, resource_data, issuer, dependency) } pub fn module_rule_matcher_inner<'a>( module_rule: &'a ModuleRule, resource_data: &ResourceData, issuer: Option<&str>, + dependency: &DependencyCategory, ) -> Result> { // Include all modules that pass test assertion. If you supply a Rule.test option, you cannot also supply a `Rule.resource`. // See: https://webpack.js.org/configuration/module/#ruletest @@ -66,9 +69,14 @@ pub fn module_rule_matcher_inner<'a>( return Ok(None); } + if let Some(dependency_rule) = &module_rule.dependency + && !dependency_rule.try_match(&dependency.to_string())? { + return Ok(None); + } + if let Some(one_of) = &module_rule.one_of { for rule in one_of { - if let Some(rule) = module_rule_matcher_inner(rule, resource_data, issuer)? { + if let Some(rule) = module_rule_matcher_inner(rule, resource_data, issuer, dependency)? { return Ok(Some(rule)); } } diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index e45e2efbc896..d6adf4e057fe 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -264,6 +264,10 @@ const getRawModuleRule = ( test: rule.test ? getRawRuleSetCondition(rule.test) : undefined, include: rule.include ? getRawRuleSetCondition(rule.include) : undefined, exclude: rule.exclude ? getRawRuleSetCondition(rule.exclude) : undefined, + issuer: rule.issuer ? getRawRuleSetCondition(rule.issuer) : undefined, + dependency: rule.dependency + ? getRawRuleSetCondition(rule.dependency) + : undefined, resource: rule.resource ? getRawRuleSetCondition(rule.resource) : undefined, resourceQuery: rule.resourceQuery ? getRawRuleSetCondition(rule.resourceQuery) @@ -274,7 +278,6 @@ const getRawModuleRule = ( parser: rule.parser, generator: rule.generator, resolve: rule.resolve ? getRawResolve(rule.resolve) : undefined, - issuer: rule.issuer ? getRawRuleSetCondition(rule.issuer) : undefined, oneOf: rule.oneOf ? rule.oneOf.map(i => getRawModuleRule(i, options)) : undefined diff --git a/packages/rspack/src/config/defaults.ts b/packages/rspack/src/config/defaults.ts index 2deeac06c734..925db4c13b0a 100644 --- a/packages/rspack/src/config/defaults.ts +++ b/packages/rspack/src/config/defaults.ts @@ -235,7 +235,6 @@ const applyModuleDefaults = ( } ] }); - if (asyncWebAssembly) { const wasm = { type: "webassembly/async", @@ -255,6 +254,18 @@ const applyModuleDefaults = ( ...wasm }); } + rules.push({ + dependency: "url", + oneOf: [ + // { + // scheme: /^data$/, + // type: "asset/inline" + // }, + { + type: "asset/resource" + } + ] + }); return rules; }); diff --git a/packages/rspack/src/config/schema.js b/packages/rspack/src/config/schema.js index 1ecdaace0aec..446f5e4ed29e 100644 --- a/packages/rspack/src/config/schema.js +++ b/packages/rspack/src/config/schema.js @@ -1384,6 +1384,14 @@ module.exports = { } ] }, + dependency: { + description: "Match dependency type.", + oneOf: [ + { + $ref: "#/definitions/RuleSetConditionOrConditions" + } + ] + }, oneOf: { description: "Only execute the first matching rule in this array.", type: "array", diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index 5167c4054ffa..1042eaa4052e 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -260,6 +260,7 @@ export interface RuleSetRule { exclude?: RuleSetCondition; include?: RuleSetCondition; issuer?: RuleSetCondition; + dependency?: RuleSetCondition; resource?: RuleSetCondition; resourceFragment?: RuleSetCondition; resourceQuery?: RuleSetCondition; diff --git a/packages/rspack/tests/Defaults.unittest.ts b/packages/rspack/tests/Defaults.unittest.ts index de9b9601c97b..9bc6f1efcedd 100644 --- a/packages/rspack/tests/Defaults.unittest.ts +++ b/packages/rspack/tests/Defaults.unittest.ts @@ -169,6 +169,14 @@ describe("snapshots", () => { ], "test": /\\\\\\.css\\$/i, }, + { + "dependency": "url", + "oneOf": [ + { + "type": "asset/resource", + }, + ], + }, ], "parser": { "asset": { diff --git a/packages/rspack/tests/case.template.ts b/packages/rspack/tests/case.template.ts index e1955719b8d0..382f12710476 100644 --- a/packages/rspack/tests/case.template.ts +++ b/packages/rspack/tests/case.template.ts @@ -78,7 +78,6 @@ export function describeCases(config: { name: string; casePath: string }) { }, ...config, // we may need to use deepMerge to handle config merge, but we may fix it until we need it output: { - publicPath: "/", // @ts-ignore ...config.output, path: outputPath diff --git a/packages/rspack/tests/cases/css/rewrite-url-with-css-filename/webpack.config.js b/packages/rspack/tests/cases/css/rewrite-url-with-css-filename/webpack.config.js index af52038ba9f7..50850ff5f1be 100644 --- a/packages/rspack/tests/cases/css/rewrite-url-with-css-filename/webpack.config.js +++ b/packages/rspack/tests/cases/css/rewrite-url-with-css-filename/webpack.config.js @@ -1,5 +1,6 @@ module.exports = { output: { + publicPath: "/", cssFilename: "css/[name].css" }, resolve: { diff --git a/packages/rspack/tests/cases/css/rewrite-url/index.js b/packages/rspack/tests/cases/css/rewrite-url/index.js index 49abdfbb183e..3b1143cc0702 100644 --- a/packages/rspack/tests/cases/css/rewrite-url/index.js +++ b/packages/rspack/tests/cases/css/rewrite-url/index.js @@ -8,7 +8,7 @@ it("should rewrite the css url()", function () { expect(a.startsWith("./")).toBe(false); expect(a.includes("./logo.png")).toBe(false); expect(a.endsWith(".png")).toBe(true); - expect(a === "/19f79ab99b58da29.png").toBe(true); + expect(a === "19f79ab99b58da29.png").toBe(true); const b = /b: url\((.*)\);/.exec(css)[1]; expect(b).toBe( '"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAOCAYAAAAbvf3sAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEWSURBVHgBjZFRSsNAEIb/3fQAOUK9gfpe6YIovjU3UE+gJ2g8gTcw3sC+itUFrW+BeAN7ATFSKAjbHWcSC+mSlszLDrvfzvzzj0LHmNpZBtBQd4Gf7fuYgHPPueoCe1DK1UsHd7Dzw+P0daQj/SC5h09OzdGktw221vZXUBlVMN0ILPd6O9yzBBXX8CBdv6kWOGa4YLivQJNjM0ia73oTLsBwJjCB5gu4i7CgbsIey5ThEcOfKziTGFOGHypJeZ7jZ/Gbst4x2/fN9h2eGTNvgk92VrDEWLNmfJXLpIYrRy5b4Fs+9v2/pL0oUncI7FuHLI6PK5lJZGoe8qXNvrry27DesoRLpLPmNkTw9yEcxPWJMR+S/AFbfpAZqxwUNQAAAABJRU5ErkJggg=="' diff --git a/packages/rspack/tests/cases/module-variables/webpack-public-path/webpack.config.js b/packages/rspack/tests/cases/module-variables/webpack-public-path/webpack.config.js new file mode 100644 index 000000000000..ef3273fb610e --- /dev/null +++ b/packages/rspack/tests/cases/module-variables/webpack-public-path/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + output: { + publicPath: "/" + } +}; diff --git a/packages/rspack/tests/configCases/asset-url/target-node1/index.css b/packages/rspack/tests/configCases/asset-url/target-node1/index.css new file mode 100644 index 000000000000..077f6dd7c017 --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node1/index.css @@ -0,0 +1 @@ +a {} diff --git a/packages/rspack/tests/configCases/asset-url/target-node1/index.js b/packages/rspack/tests/configCases/asset-url/target-node1/index.js new file mode 100644 index 000000000000..67f1fa946989 --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node1/index.js @@ -0,0 +1,7 @@ +const currentDir = require("url").pathToFileURL(__dirname); + +it("should handle import.meta.url in URL()", () => { + const { href } = new URL("./index.css", import.meta.url); + + expect(href).toBe(currentDir + "/public/index.css"); +}); diff --git a/packages/rspack/tests/configCases/asset-url/target-node1/webpack.config.js b/packages/rspack/tests/configCases/asset-url/target-node1/webpack.config.js new file mode 100644 index 000000000000..72a8ac76b308 --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node1/webpack.config.js @@ -0,0 +1,19 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + target: "node", + devtool: false, + output: { + assetModuleFilename: "[name][ext]", + publicPath: "public/" + }, + module: { + rules: [ + { + test: /\.css$/, + dependency: ["esm", "commonjs"], + use: "url-loader" + } + ] + } +}; diff --git a/packages/rspack/tests/configCases/asset-url/target-node2/index.css b/packages/rspack/tests/configCases/asset-url/target-node2/index.css new file mode 100644 index 000000000000..077f6dd7c017 --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node2/index.css @@ -0,0 +1 @@ +a {} diff --git a/packages/rspack/tests/configCases/asset-url/target-node2/index.js b/packages/rspack/tests/configCases/asset-url/target-node2/index.js new file mode 100644 index 000000000000..434cac684a7d --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node2/index.js @@ -0,0 +1,7 @@ +const currentDir = require("url").pathToFileURL(__dirname); + +it("should handle import.meta.url in URL()", () => { + const { href } = new URL("./index.css", import.meta.url); + + expect(href).toBe(currentDir + "/index.css"); +}); diff --git a/packages/rspack/tests/configCases/asset-url/target-node2/webpack.config.js b/packages/rspack/tests/configCases/asset-url/target-node2/webpack.config.js new file mode 100644 index 000000000000..14934d1135f6 --- /dev/null +++ b/packages/rspack/tests/configCases/asset-url/target-node2/webpack.config.js @@ -0,0 +1,9 @@ +/** @type {import("../../../../").Configuration} */ +module.exports = { + mode: "development", + target: "node", + devtool: false, + output: { + assetModuleFilename: "[name][ext]" + } +}; diff --git a/packages/rspack/tests/configCases/rule-set/generator/webpack.config.js b/packages/rspack/tests/configCases/rule-set/generator/webpack.config.js index 6774b3898bcc..acd15ff89933 100644 --- a/packages/rspack/tests/configCases/rule-set/generator/webpack.config.js +++ b/packages/rspack/tests/configCases/rule-set/generator/webpack.config.js @@ -4,6 +4,7 @@ module.exports = { context: __dirname, output: { + publicPath: "/", assetModuleFilename: "asset/[name][ext]" }, module: { diff --git a/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/module.js b/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/module.js index 65dfded5eee7..4c6a7f35a5b3 100644 --- a/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/module.js +++ b/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/module.js @@ -1,3 +1,3 @@ -export default () => new Worker(new URL("./chunk2.js", import.meta.url)); // TODO(ahabhgk): should be "./chunk2" WorkerDependency instead of URLDependency +export default () => new Worker(new URL("./chunk2", import.meta.url)); --- export default 42; diff --git a/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/webpack.config.js b/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/webpack.config.js new file mode 100644 index 000000000000..d17724241795 --- /dev/null +++ b/packages/rspack/tests/hotCases/disposing/remove-chunk-with-shared-in-other-runtime/webpack.config.js @@ -0,0 +1,18 @@ +// TODO(ahabhgk): needs WorkerDependency +module.exports = { + module: { + rules: [ + { + dependency: "url", + type: 'js', + } + ] + }, + resolve: { + byDependency: { + url: { + extensions: ['.js'] + } + } + } +}