From 61f0cd2b4e313445a9d3329ca71240e99edfb352 Mon Sep 17 00:00:00 2001 From: inottn Date: Tue, 17 Dec 2024 12:19:27 +0800 Subject: [PATCH] feat: support module.generator.outputPath (#8554) --- 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 840cb9d2b0b..36af31a5e85 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1126,6 +1126,7 @@ export interface RawAssetGeneratorDataUrlOptions { export interface RawAssetGeneratorOptions { emit?: boolean filename?: JsFilename + outputPath?: JsFilename publicPath?: "auto" | JsFilename dataUrl?: RawAssetGeneratorDataUrlOptions | ((source: Buffer, context: RawAssetGeneratorDataUrlFnCtx) => string) } @@ -1150,6 +1151,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 66f0785fd5a..6044dbe6421 100644 --- a/crates/rspack_binding_options/src/options/raw_module/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_module/mod.rs @@ -496,6 +496,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, #[debug(skip)] @@ -510,6 +511,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 @@ -543,6 +545,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, } @@ -552,6 +555,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 3f7311b6a7b..9579dc6c5b1 100644 --- a/crates/rspack_core/src/options/module.rs +++ b/crates/rspack_core/src/options/module.rs @@ -333,6 +333,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() @@ -370,6 +381,7 @@ pub struct AssetInlineGeneratorOptions { pub struct AssetResourceGeneratorOptions { pub emit: Option, pub filename: Option, + pub output_path: Option, pub public_path: Option, } @@ -378,6 +390,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 6199e69f857..6d981fc187c 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::*; @@ -242,22 +242,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( @@ -447,12 +463,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) = @@ -472,13 +489,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); @@ -565,12 +586,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 54e914d2a22..44c0b52eaee 100644 --- a/packages/rspack/etc/core.api.md +++ b/packages/rspack/etc/core.api.md @@ -181,6 +181,9 @@ export type AssetInlineGeneratorOptions = { // @public export type AssetModuleFilename = Filename; +// @public +export type AssetModuleOutputPath = Filename; + // @public export type AssetParserDataUrl = AssetParserDataUrlOptions; @@ -198,6 +201,7 @@ export type AssetParserOptions = { export type AssetResourceGeneratorOptions = { emit?: boolean; filename?: Filename; + outputPath?: AssetModuleOutputPath; publicPath?: PublicPath; }; @@ -5316,6 +5320,7 @@ declare namespace rspackExports { AssetGeneratorDataUrlFunction, AssetGeneratorDataUrl, AssetInlineGeneratorOptions, + AssetModuleOutputPath, AssetResourceGeneratorOptions, AssetGeneratorOptions, CssGeneratorExportsConvention, @@ -10460,6 +10465,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 16f79a57300..516a1c48897 100644 --- a/packages/rspack/src/config/adapter.ts +++ b/packages/rspack/src/config/adapter.ts @@ -815,6 +815,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 fc1900ab259..879843399e4 100644 --- a/packages/rspack/src/config/types.ts +++ b/packages/rspack/src/config/types.ts @@ -1128,6 +1128,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 = { /** @@ -1139,6 +1142,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 b89fdbd1d14..39d0adfd659 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 dace96c21c8..bc72a3fe294 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) 一样。