diff --git a/crates/node_binding/napi-binding.d.ts b/crates/node_binding/napi-binding.d.ts index c5ffae74c515..c33ecc7845bf 100644 --- a/crates/node_binding/napi-binding.d.ts +++ b/crates/node_binding/napi-binding.d.ts @@ -1732,7 +1732,7 @@ export interface RawAssetGeneratorOptions { outputPath?: JsFilename publicPath?: "auto" | JsFilename dataUrl?: RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string) - importMode?: "url" | "preserve" + importMode?: "url" | "preserve" | "newURL" binary?: boolean } @@ -1759,7 +1759,7 @@ export interface RawAssetResourceGeneratorOptions { filename?: JsFilename outputPath?: JsFilename publicPath?: "auto" | JsFilename - importMode?: "url" | "preserve" + importMode?: "url" | "preserve" | "newURL" binary?: boolean } diff --git a/crates/rspack_binding_api/src/raw_options/raw_module/mod.rs b/crates/rspack_binding_api/src/raw_options/raw_module/mod.rs index 2233fbc237e8..aa8b415bfb6b 100644 --- a/crates/rspack_binding_api/src/raw_options/raw_module/mod.rs +++ b/crates/rspack_binding_api/src/raw_options/raw_module/mod.rs @@ -575,7 +575,7 @@ pub struct RawAssetGeneratorOptions { )] pub data_url: Option, - #[napi(ts_type = r#""url" | "preserve""#)] + #[napi(ts_type = r#""url" | "preserve" | "newURL""#)] pub import_mode: Option, pub binary: Option, } @@ -626,7 +626,7 @@ pub struct RawAssetResourceGeneratorOptions { pub output_path: Option, #[napi(ts_type = "\"auto\" | JsFilename")] pub public_path: Option, - #[napi(ts_type = r#""url" | "preserve""#)] + #[napi(ts_type = r#""url" | "preserve" | "newURL""#)] pub import_mode: Option, pub binary: Option, } diff --git a/crates/rspack_core/src/options/module.rs b/crates/rspack_core/src/options/module.rs index 8258bdba9c91..2a7173345fe1 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -504,6 +504,7 @@ bitflags! { impl AssetGeneratorImportModeFlags: u8 { const URL = 1 << 0; const PRESERVE = 1 << 1; + const NEW_URL = 1 << 2; } } @@ -518,6 +519,9 @@ impl AssetGeneratorImportMode { pub fn is_preserve(&self) -> bool { self.0.contains(AssetGeneratorImportModeFlags::PRESERVE) } + pub fn is_new_url(&self) -> bool { + self.0.contains(AssetGeneratorImportModeFlags::NEW_URL) + } } impl From for AssetGeneratorImportMode { @@ -525,7 +529,8 @@ impl From for AssetGeneratorImportMode { match s.as_str() { "url" => Self(AssetGeneratorImportModeFlags::URL), "preserve" => Self(AssetGeneratorImportModeFlags::PRESERVE), - _ => unreachable!("AssetGeneratorImportMode error"), + "newURL" => Self(AssetGeneratorImportModeFlags::NEW_URL), + _ => unreachable!("asset generator import mode should be url, preserve or newURL"), } } } diff --git a/crates/rspack_plugin_asset/src/lib.rs b/crates/rspack_plugin_asset/src/lib.rs index 2357bf75f58b..d644feee0620 100644 --- a/crates/rspack_plugin_asset/src/lib.rs +++ b/crates/rspack_plugin_asset/src/lib.rs @@ -562,7 +562,7 @@ impl ParserAndGenerator for AssetParserAndGenerator { ) .await?; - let asset_path = if import_mode.is_preserve() { + let asset_path = if import_mode.is_preserve() || import_mode.is_new_url() { generate_context .data .insert(CodeGenerationPublicPathAutoReplace(true)); @@ -631,37 +631,69 @@ impl ParserAndGenerator for AssetParserAndGenerator { return Ok(RawStringSource::from("").boxed()); } - if import_mode.is_preserve() && parsed_asset_config.is_resource() { - let is_module = compilation.options.output.module; - if let Some(ref mut scope) = generate_context.concatenation_scope { - scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); - if is_module { + if parsed_asset_config.is_resource() { + if import_mode.is_preserve() { + let is_module = compilation.options.output.module; + if let Some(ref mut scope) = generate_context.concatenation_scope { + scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); + if is_module { + return Ok( + RawStringSource::from(format!( + r#"import {NAMESPACE_OBJECT_EXPORT} from {exported_content};"# + )) + .boxed(), + ); + } else { + let supports_const = compilation.options.output.environment.supports_const(); + let declaration_kind = if supports_const { "const" } else { "var" }; + return Ok( + RawStringSource::from(format!( + r#"{declaration_kind} {NAMESPACE_OBJECT_EXPORT} = require({exported_content});"# + )) + .boxed(), + ); + } + } else { + generate_context + .runtime_requirements + .insert(RuntimeGlobals::MODULE); return Ok( - RawStringSource::from(format!( - r#"import {NAMESPACE_OBJECT_EXPORT} from {exported_content};"# - )) - .boxed(), + RawStringSource::from(format!(r#"module.exports = require({exported_content});"#)) + .boxed(), ); + } + }; + + if import_mode.is_new_url() { + let supports_arrow_function = compilation + .options + .output + .environment + .supports_arrow_function(); + let url_function_call = if supports_arrow_function { + format!(r#"/*#__PURE__*/(()=>new URL({exported_content}, import.meta.url).href)()"#) } else { + format!( + r#"/*#__PURE__*/(function(){{return new URL({exported_content}, import.meta.url).href}})()"# + ) + }; + if let Some(ref mut scope) = generate_context.concatenation_scope { let supports_const = compilation.options.output.environment.supports_const(); let declaration_kind = if supports_const { "const" } else { "var" }; + scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); return Ok( RawStringSource::from(format!( - r#"{declaration_kind} {NAMESPACE_OBJECT_EXPORT} = require({exported_content});"# + r#"{declaration_kind} {NAMESPACE_OBJECT_EXPORT} = {url_function_call};"# )) .boxed(), ); + } else { + return Ok( + RawStringSource::from(format!(r#"module.exports = {url_function_call};"#)).boxed(), + ); } - } else { - generate_context - .runtime_requirements - .insert(RuntimeGlobals::MODULE); - return Ok( - RawStringSource::from(format!(r#"module.exports = require({exported_content});"#)) - .boxed(), - ); } - }; + } if let Some(ref mut scope) = generate_context.concatenation_scope { scope.register_namespace_export(NAMESPACE_OBJECT_EXPORT); diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 4a3c9acb06c1..7dc3440acb01 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -237,7 +237,7 @@ export type AssetInlineGeneratorOptions = { export type AssetModuleFilename = Filename; // @public -export type AssetModuleImportMode = "url" | "preserve"; +export type AssetModuleImportMode = "url" | "preserve" | "newURL"; // @public export type AssetModuleOutputPath = Filename; diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index 81a8a087215d..aa342f0955bc 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -1202,7 +1202,7 @@ export type AssetModuleOutputPath = Filename; * Only for modules with module type 'asset' or 'asset/resource'. * @default "url" */ -export type AssetModuleImportMode = "url" | "preserve"; +export type AssetModuleImportMode = "url" | "preserve" | "newURL"; /** Options for asset modules. */ export type AssetResourceGeneratorOptions = { @@ -1224,6 +1224,7 @@ export type AssetResourceGeneratorOptions = { /** * If "url", a URL pointing to the asset will be generated based on publicPath. * If "preserve", preserve import/require statement from generated asset. + * If "newURL", a new URL object will be created for the asset. * Only for modules with module type 'asset' or 'asset/resource'. * @default "url" */ diff --git a/tests/rspack-test/configCases/asset/preserve-import/img.png b/tests/rspack-test/configCases/asset/import-mode-new-url/img.png similarity index 100% rename from tests/rspack-test/configCases/asset/preserve-import/img.png rename to tests/rspack-test/configCases/asset/import-mode-new-url/img.png diff --git a/tests/rspack-test/configCases/asset/import-mode-new-url/rspack.config.js b/tests/rspack-test/configCases/asset/import-mode-new-url/rspack.config.js new file mode 100644 index 000000000000..74a01d68d4fe --- /dev/null +++ b/tests/rspack-test/configCases/asset/import-mode-new-url/rspack.config.js @@ -0,0 +1,44 @@ +const { rspack } = require("@rspack/core"); +const assert = require("assert"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + context: __dirname, + entry: { + index: "./img.png" + }, + module: { + rules: [ + { + test: /\.png$/, + type: "asset/resource", + generator: { + importMode: "newURL" + } + } + ], + }, + plugins: [ + new rspack.BannerPlugin({ + banner: 'var __import__meta__url__ = {};', + stage: 0 + }), + new rspack.DefinePlugin({ + 'import.meta.url': '__import__meta__url__' + }), + new (class { + apply(compiler) { + compiler.hooks.compilation.tap("MyPlugin", compilation => { + compilation.hooks.processAssets.tap("MyPlugin", assets => { + let list = Object.keys(assets); + const js = list.find(item => item.endsWith("js")); + const jsContent = assets[js].source().toString(); + console.log(jsContent, 22222222) + + assert(/new URL\(['"]\.\/(\w*)\.png['"], import\.meta\.url\)/.test(jsContent)); + }); + }); + } + })() + ] +}; diff --git a/tests/rspack-test/configCases/asset/preserve-import/test.config.js b/tests/rspack-test/configCases/asset/import-mode-new-url/test.config.js similarity index 100% rename from tests/rspack-test/configCases/asset/preserve-import/test.config.js rename to tests/rspack-test/configCases/asset/import-mode-new-url/test.config.js diff --git a/tests/rspack-test/configCases/asset/import-mode-preserve/img.png b/tests/rspack-test/configCases/asset/import-mode-preserve/img.png new file mode 100644 index 000000000000..b74b839e2b86 Binary files /dev/null and b/tests/rspack-test/configCases/asset/import-mode-preserve/img.png differ diff --git a/tests/rspack-test/configCases/asset/preserve-import/rspack.config.js b/tests/rspack-test/configCases/asset/import-mode-preserve/rspack.config.js similarity index 100% rename from tests/rspack-test/configCases/asset/preserve-import/rspack.config.js rename to tests/rspack-test/configCases/asset/import-mode-preserve/rspack.config.js diff --git a/tests/rspack-test/configCases/asset/import-mode-preserve/test.config.js b/tests/rspack-test/configCases/asset/import-mode-preserve/test.config.js new file mode 100644 index 000000000000..08ea6c319c8e --- /dev/null +++ b/tests/rspack-test/configCases/asset/import-mode-preserve/test.config.js @@ -0,0 +1 @@ +exports.noTests = true; diff --git a/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/img.png b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/img.png new file mode 100644 index 000000000000..b74b839e2b86 Binary files /dev/null and b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/img.png differ diff --git a/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/rspack.config.js b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/rspack.config.js new file mode 100644 index 000000000000..671c695b9bd4 --- /dev/null +++ b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/rspack.config.js @@ -0,0 +1,63 @@ +const assert = require("assert"); + +/** @type {import("@rspack/core").Configuration} */ +module.exports = { + context: __dirname, + entry: { + index: "./img.png" + }, + output: { + filename: `[name].js`, + chunkFilename: `async.js`, + module: true, + library: { + type: "modern-module" + }, + iife: false, + chunkFormat: "module", + chunkLoading: "import" + }, + module: { + rules: [ + { + test: /\.png$/, + type: "asset/resource", + generator: { + filename: "static/img/[name].png", + importMode: "newURL" + } + } + ] + }, + experiments: { + outputModule: true + }, + optimization: { + concatenateModules: true, + avoidEntryIife: true, + minimize: false + }, + plugins: [ + new (class { + apply(compiler) { + compiler.hooks.compilation.tap("MyPlugin", compilation => { + compilation.hooks.processAssets.tap("MyPlugin", assets => { + let list = Object.keys(assets); + const js = list.find(item => item.endsWith("js")); + const jsContent = assets[js].source().toString(); + + const referenceWithNewURL = + /const\simg_namespaceObject\s=.*new\sURL\(['"]\.\/static\/img\/img\.png['"]/.test( + jsContent + ); + + assert(referenceWithNewURL); + const hasExports = + /export\s{\simg_namespaceObject\sas\sdefault\s}/.test(jsContent); + assert(hasExports); + }); + }); + } + })() + ] +}; diff --git a/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/test.config.js b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/test.config.js new file mode 100644 index 000000000000..08ea6c319c8e --- /dev/null +++ b/tests/rspack-test/configCases/library/modern-module-asset-entry-new-url/test.config.js @@ -0,0 +1 @@ +exports.noTests = true;