From 33f5ca1768388c1b25c2de6a1d131ed3546cdf09 Mon Sep 17 00:00:00 2001 From: inottn Date: Sun, 8 Dec 2024 13:51:07 +0800 Subject: [PATCH] feat: support module.generator.outputPath --- crates/node_binding/binding.d.ts | 2 + .../src/options/raw_module/mod.rs | 4 ++ crates/rspack_core/src/options/module.rs | 13 +++++ crates/rspack_plugin_asset/src/lib.rs | 54 +++++++++++++------ packages/rspack/etc/core.api.md | 6 +++ packages/rspack/src/config/adapter.ts | 1 + packages/rspack/src/config/types.ts | 6 +++ .../rule-generator-outputPath/test.filter.js | 1 - website/docs/en/config/module.mdx | 13 +++++ website/docs/zh/config/module.mdx | 13 +++++ 10 files changed, 96 insertions(+), 17 deletions(-) delete mode 100644 tests/webpack-test/configCases/asset-modules/rule-generator-outputPath/test.filter.js diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 3c1ddde7f38..e069415f43a 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1111,6 +1111,7 @@ export interface RawAssetGeneratorDataUrlOptions { export interface RawAssetGeneratorOptions { emit?: boolean filename?: JsFilename + outputPath?: JsFilename publicPath?: "auto" | JsFilename dataUrl?: RawAssetGeneratorDataUrlOptions | ((arg: RawAssetGeneratorDataUrlFnArgs) => string) } @@ -1135,6 +1136,7 @@ export interface RawAssetParserOptions { export interface RawAssetResourceGeneratorOptions { emit?: boolean filename?: JsFilename + outputPath?: JsFilename publicPath?: "auto" | JsFilename } 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 782e1023077..b4109d0a42d 100644 --- a/crates/rspack_binding_options/src/options/raw_module/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_module/mod.rs @@ -497,6 +497,7 @@ impl From for GeneratorOptions { pub struct RawAssetGeneratorOptions { pub emit: Option, pub filename: Option, + pub output_path: Option, #[napi(ts_type = "\"auto\" | JsFilename")] pub public_path: Option, #[derivative(Debug = "ignore")] @@ -511,6 +512,7 @@ impl From for AssetGeneratorOptions { Self { emit: value.emit, filename: value.filename.map(|i| i.into()), + output_path: value.output_path.map(|i| i.into()), public_path: value.public_path.map(|i| i.into()), data_url: value .data_url @@ -545,6 +547,7 @@ impl From for AssetInlineGeneratorOptions { pub struct RawAssetResourceGeneratorOptions { pub emit: Option, pub filename: Option, + pub output_path: Option, #[napi(ts_type = "\"auto\" | JsFilename")] pub public_path: Option, } @@ -554,6 +557,7 @@ impl From for AssetResourceGeneratorOptions { Self { emit: value.emit, filename: value.filename.map(|i| i.into()), + output_path: value.output_path.map(|i| i.into()), public_path: value.public_path.map(|i| i.into()), } } diff --git a/crates/rspack_core/src/options/module.rs b/crates/rspack_core/src/options/module.rs index a8b1e70bf98..b11786c0c70 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -334,6 +334,17 @@ impl GeneratorOptions { .or_else(|| self.get_asset_resource().and_then(|x| x.filename.as_ref())) } + pub fn asset_output_path(&self) -> Option<&Filename> { + self + .get_asset() + .and_then(|x| x.output_path.as_ref()) + .or_else(|| { + self + .get_asset_resource() + .and_then(|x| x.output_path.as_ref()) + }) + } + pub fn asset_public_path(&self) -> Option<&PublicPath> { self .get_asset() @@ -371,6 +382,7 @@ pub struct AssetInlineGeneratorOptions { pub struct AssetResourceGeneratorOptions { pub emit: Option, pub filename: Option, + pub output_path: Option, pub public_path: Option, } @@ -379,6 +391,7 @@ pub struct AssetResourceGeneratorOptions { pub struct AssetGeneratorOptions { pub emit: Option, pub filename: Option, + pub output_path: Option, pub public_path: Option, pub data_url: Option, } diff --git a/crates/rspack_plugin_asset/src/lib.rs b/crates/rspack_plugin_asset/src/lib.rs index 1f5d1198e3f..88698c39b4a 100644 --- a/crates/rspack_plugin_asset/src/lib.rs +++ b/crates/rspack_plugin_asset/src/lib.rs @@ -1,6 +1,6 @@ #![feature(let_chains)] -use std::{borrow::Cow, hash::Hasher}; +use std::{borrow::Cow, hash::Hasher, path::PathBuf}; use async_trait::async_trait; use rayon::prelude::*; @@ -241,22 +241,38 @@ impl AssetParserAndGenerator { compilation: &Compilation, contenthash: Option<&str>, source_file_name: &str, - ) -> Result<(String, AssetInfo)> { + use_output_path: bool, + ) -> Result<(String, String, AssetInfo)> { // Use [Rule.generator.filename] if it is set, otherwise use [output.assetModuleFilename]. let asset_filename_template = module_generator_options .and_then(|x| x.asset_filename()) .unwrap_or(&compilation.options.output.asset_module_filename); + let path_data = PathData::default() + .module_id_optional( + ChunkGraph::get_module_id(&compilation.module_ids, module.id()).map(|s| s.as_str()), + ) + .content_hash_optional(contenthash) + .hash_optional(contenthash) + .filename(source_file_name); + + let (mut filename, mut asset_info) = + compilation.get_asset_path_with_info(asset_filename_template, path_data)?; + let original_filename = filename.clone(); + + if use_output_path { + let output_path = module_generator_options.and_then(|x| x.asset_output_path()); + + if let Some(output_path) = output_path { + let (output_path, another_asset_info) = + compilation.get_asset_path_with_info(output_path, path_data)?; + let output_path = PathBuf::from(output_path); + let file_path = PathBuf::from(filename); + filename = output_path.join(file_path).to_string_lossy().to_string(); + asset_info.merge_another_asset(another_asset_info); + } + } - compilation.get_asset_path_with_info( - asset_filename_template, - PathData::default() - .module_id_optional( - ChunkGraph::get_module_id(&compilation.module_ids, module.id()).map(|s| s.as_str()), - ) - .content_hash_optional(contenthash) - .hash_optional(contenthash) - .filename(source_file_name), - ) + Ok((original_filename, filename, asset_info)) } fn get_public_path( @@ -444,12 +460,13 @@ impl ParserAndGenerator for AssetParserAndGenerator { let contenthash = contenthash.rendered(compilation.options.output.hash_digest_length); let source_file_name = self.get_source_file_name(normal_module, compilation); - let (filename, mut asset_info) = self.get_asset_module_filename( + let (original_filename, filename, mut asset_info) = self.get_asset_module_filename( normal_module, module_generator_options, compilation, Some(contenthash), &source_file_name, + true, )?; let asset_path = if let Some(public_path) = @@ -469,13 +486,17 @@ impl ParserAndGenerator for AssetParserAndGenerator { } PublicPath::Auto => public_path.render(compilation, &filename), }; - serde_json::to_string(&format!("{public_path}{filename}")) + serde_json::to_string(&format!("{public_path}{original_filename}")) .map_err(|e| error!(e.to_string()))? } else { generate_context .runtime_requirements .insert(RuntimeGlobals::PUBLIC_PATH); - format!(r#"{} + "{}""#, RuntimeGlobals::PUBLIC_PATH, filename) + format!( + r#"{} + "{}""#, + RuntimeGlobals::PUBLIC_PATH, + original_filename + ) }; asset_info.set_source_filename(source_file_name); @@ -562,12 +583,13 @@ impl ParserAndGenerator for AssetParserAndGenerator { data_url_options.dyn_hash(hasher); } else if parsed_asset_config.is_resource() { let source_file_name = self.get_source_file_name(module, compilation); - let (filename, _) = self.get_asset_module_filename( + let (filename, _, _) = self.get_asset_module_filename( module, module_generator_options, compilation, None, &source_file_name, + false, )?; filename.dyn_hash(hasher); match module_generator_options.and_then(|x| x.asset_public_path()) { diff --git a/packages/rspack/etc/core.api.md b/packages/rspack/etc/core.api.md index 9e82a8771b7..671260768af 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -179,6 +179,9 @@ export type AssetInlineGeneratorOptions = { // @public export type AssetModuleFilename = Filename; +// @public +export type AssetModuleOutputPath = Filename; + // @public export type AssetParserDataUrl = AssetParserDataUrlOptions; @@ -196,6 +199,7 @@ export type AssetParserOptions = { export type AssetResourceGeneratorOptions = { emit?: boolean; filename?: Filename; + outputPath?: AssetModuleOutputPath; publicPath?: PublicPath; }; @@ -5242,6 +5246,7 @@ declare namespace rspackExports { AssetGeneratorDataUrlFunction, AssetGeneratorDataUrl, AssetInlineGeneratorOptions, + AssetModuleOutputPath, AssetResourceGeneratorOptions, AssetGeneratorOptions, CssGeneratorExportsConvention, @@ -10369,6 +10374,7 @@ declare namespace t { AssetGeneratorDataUrlFunction, AssetGeneratorDataUrl, AssetInlineGeneratorOptions, + AssetModuleOutputPath, AssetResourceGeneratorOptions, AssetGeneratorOptions, CssGeneratorExportsConvention, diff --git a/packages/rspack/src/config/adapter.ts b/packages/rspack/src/config/adapter.ts index 8559366b5cd..388e43b4651 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -813,6 +813,7 @@ function getRawAssetResourceGeneratorOptions( return { emit: options.emit, filename: options.filename, + outputPath: options.outputPath, publicPath: options.publicPath }; } diff --git a/packages/rspack/src/config/types.ts b/packages/rspack/src/config/types.ts index 41f5db7cafd..b71ebec540b 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -1125,6 +1125,9 @@ export type AssetInlineGeneratorOptions = { dataUrl?: AssetGeneratorDataUrl; }; +/** Emit the asset in the specified folder relative to 'output.path'. */ +export type AssetModuleOutputPath = Filename; + /** Options for asset modules. */ export type AssetResourceGeneratorOptions = { /** @@ -1136,6 +1139,9 @@ export type AssetResourceGeneratorOptions = { /** This option determines the name of each asset resource output bundle.*/ filename?: Filename; + /** Emit the asset in the specified folder relative to 'output.path' */ + outputPath?: AssetModuleOutputPath; + /** This option determines the URL prefix of the referenced 'asset' or 'asset/resource'*/ publicPath?: PublicPath; }; diff --git a/tests/webpack-test/configCases/asset-modules/rule-generator-outputPath/test.filter.js b/tests/webpack-test/configCases/asset-modules/rule-generator-outputPath/test.filter.js deleted file mode 100644 index 962468a2172..00000000000 --- a/tests/webpack-test/configCases/asset-modules/rule-generator-outputPath/test.filter.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = () => { return 'https://github.com/web-infra-dev/rspack/issues/8539' } diff --git a/website/docs/en/config/module.mdx b/website/docs/en/config/module.mdx index 48092a0ba18..f4bf3e48be3 100644 --- a/website/docs/en/config/module.mdx +++ b/website/docs/en/config/module.mdx @@ -506,6 +506,15 @@ module.exports = { }; ``` +#### module.generator.asset.outputPath + +- **Type:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` +- **Default:** `undefined` + +Emit the asset in the specified folder relative to [`output.path`](/config/output#outputpath). + +Only for modules with module type `'asset'` or `'asset/resource'`. + #### module.generator.asset.publicPath - **Type:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` @@ -586,6 +595,10 @@ Generator options for `asset/resource` modules. Same as [`module.generator["asset"].filename`](#modulegeneratorassetfilename). +#### module.generator["asset/resource"].outputPath + +Same as [`module.generator["asset"].outputPath`](#modulegeneratorassetoutputpath). + #### module.generator["asset/resource"].publicPath Same as [`module.generator["asset"].publicPath`](#modulegeneratorassetpublicpath). diff --git a/website/docs/zh/config/module.mdx b/website/docs/zh/config/module.mdx index 0129f0a4610..cbbcd3a71e8 100644 --- a/website/docs/zh/config/module.mdx +++ b/website/docs/zh/config/module.mdx @@ -506,6 +506,15 @@ module.exports = { }; ``` +#### module.generator.asset.outputPath + +- **类型:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` +- **默认值:** `undefined` + +将 asset 输出到指定文件夹,该文件夹相对于 [`output.path`](/config/output#outputpath)。 + +仅对模块类型为 `'asset'` 和 `'asset/resource'` 的模块生效。 + #### module.generator.asset.publicPath - **类型:** `string | ((pathData: PathData, assetInfo?: AssetInfo) => string)` @@ -586,6 +595,10 @@ module.exports = { 和 [`module.generator["asset"].filename`](#modulegeneratorassetfilename) 一样。 +#### module.generator["asset/resource"].outputPath + +和 [`module.generator["asset"].outputPath`](#modulegeneratorassetoutputpath) 一样。 + #### module.generator["asset/resource"].publicPath 和 [`module.generator["asset"].publicPath`](#modulegeneratorassetpublicpath) 一样。