diff --git a/Cargo.lock b/Cargo.lock index 8103841eaaa..bbb0721c34b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3864,6 +3864,8 @@ dependencies = [ "rspack_core", "rspack_error", "rspack_hook", + "rspack_regex", + "rspack_util", "swc_core", "tracing", ] diff --git a/crates/node_binding/binding.d.ts b/crates/node_binding/binding.d.ts index 71c1ca56461..d2133e66213 100644 --- a/crates/node_binding/binding.d.ts +++ b/crates/node_binding/binding.d.ts @@ -1499,6 +1499,12 @@ export interface RawStatsOptions { colors: boolean } +export interface RawSwcCssMinimizerRspackPluginOptions { + test?: string | RegExp | (string | RegExp)[] + include?: string | RegExp | (string | RegExp)[] + exclude?: string | RegExp | (string | RegExp)[] +} + export interface RawSwcJsMinimizerRspackPluginOptions { extractComments?: RawExtractComments compress: any diff --git a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs index 000e0f9feb8..b1415f5b914 100644 --- a/crates/rspack_binding_options/src/options/raw_builtins/mod.rs +++ b/crates/rspack_binding_options/src/options/raw_builtins/mod.rs @@ -11,11 +11,13 @@ mod raw_mf; mod raw_progress; mod raw_runtime_chunk; mod raw_size_limits; +mod raw_swc_css_minimizer; mod raw_swc_js_minimizer; use napi::{bindgen_prelude::FromNapiValue, Env, JsUnknown}; use napi_derive::napi; use raw_lightning_css_minimizer::RawLightningCssMinimizerRspackPluginOptions; +use raw_swc_css_minimizer::RawSwcCssMinimizerRspackPluginOptions; use rspack_core::{BoxPlugin, Plugin, PluginExt}; use rspack_error::Result; use rspack_ids::{ @@ -447,7 +449,11 @@ impl BuiltinPlugin { plugins.push(plugin); } BuiltinPluginName::SwcCssMinimizerRspackPlugin => { - plugins.push(SwcCssMinimizerRspackPlugin::default().boxed()) + let plugin = SwcCssMinimizerRspackPlugin::new( + downcast_into::(self.options)?.try_into()?, + ) + .boxed(); + plugins.push(plugin); } BuiltinPluginName::LightningCssMinimizerRspackPlugin => plugins.push( LightningCssMinimizerRspackPlugin::new( diff --git a/crates/rspack_binding_options/src/options/raw_builtins/raw_swc_css_minimizer.rs b/crates/rspack_binding_options/src/options/raw_builtins/raw_swc_css_minimizer.rs new file mode 100644 index 00000000000..e9a084c999c --- /dev/null +++ b/crates/rspack_binding_options/src/options/raw_builtins/raw_swc_css_minimizer.rs @@ -0,0 +1,62 @@ +use napi::{bindgen_prelude::Either3, Either}; +use napi_derive::napi; +use rspack_error::Result; +use rspack_napi::regexp::{JsRegExp, JsRegExpExt}; +use rspack_plugin_swc_css_minimizer::{ + SwcCssMinimizerRspackPluginOptions, SwcCssMinimizerRule, SwcCssMinimizerRules, +}; + +type RawSwcCssMinimizerRule = Either; +type RawSwcCssMinimizerRules = Either3>; +struct RawSwcCssMinimizerRuleWrapper(RawSwcCssMinimizerRule); +struct RawSwcCssMinimizerRulesWrapper(RawSwcCssMinimizerRules); + +#[derive(Debug)] +#[napi(object, object_to_js = false)] +pub struct RawSwcCssMinimizerRspackPluginOptions { + #[napi(ts_type = "string | RegExp | (string | RegExp)[]")] + pub test: Option, + #[napi(ts_type = "string | RegExp | (string | RegExp)[]")] + pub include: Option, + #[napi(ts_type = "string | RegExp | (string | RegExp)[]")] + pub exclude: Option, +} + +fn into_condition(c: Option) -> Option { + c.map(|test| RawSwcCssMinimizerRulesWrapper(test).into()) +} + +impl TryFrom for SwcCssMinimizerRspackPluginOptions { + type Error = rspack_error::Error; + + fn try_from(value: RawSwcCssMinimizerRspackPluginOptions) -> Result { + Ok(Self { + test: into_condition(value.test), + include: into_condition(value.include), + exclude: into_condition(value.exclude), + }) + } +} + +impl From for SwcCssMinimizerRule { + fn from(x: RawSwcCssMinimizerRuleWrapper) -> Self { + match x.0 { + Either::A(v) => Self::String(v), + Either::B(v) => Self::Regexp(v.to_rspack_regex()), + } + } +} + +impl From for SwcCssMinimizerRules { + fn from(value: RawSwcCssMinimizerRulesWrapper) -> Self { + match value.0 { + Either3::A(v) => Self::String(v), + Either3::B(v) => Self::Regexp(v.to_rspack_regex()), + Either3::C(v) => Self::Array( + v.into_iter() + .map(|v| RawSwcCssMinimizerRuleWrapper(v).into()) + .collect(), + ), + } + } +} diff --git a/crates/rspack_plugin_swc_css_minimizer/Cargo.toml b/crates/rspack_plugin_swc_css_minimizer/Cargo.toml index a9292f476ca..6ea82c41daf 100644 --- a/crates/rspack_plugin_swc_css_minimizer/Cargo.toml +++ b/crates/rspack_plugin_swc_css_minimizer/Cargo.toml @@ -16,6 +16,8 @@ rspack_error = { path = "../rspack_error" } rspack_hook = { path = "../rspack_hook" } swc_core = { workspace = true, features = ["css_codegen", "css_parser", "css_minifier"] } tracing = { workspace = true } +rspack_regex = { path = "../rspack_regex" } +rspack_util = { path = "../rspack_util" } [package.metadata.cargo-shear] ignored = ["tracing"] diff --git a/crates/rspack_plugin_swc_css_minimizer/src/lib.rs b/crates/rspack_plugin_swc_css_minimizer/src/lib.rs index 3b15b3b206e..ae331bb1326 100644 --- a/crates/rspack_plugin_swc_css_minimizer/src/lib.rs +++ b/crates/rspack_plugin_swc_css_minimizer/src/lib.rs @@ -6,21 +6,52 @@ use regex::Regex; use rspack_core::{rspack_sources::MapOptions, Compilation, CompilationProcessAssets, Plugin}; use rspack_error::Result; use rspack_hook::{plugin, plugin_hook}; +use rspack_regex::RspackRegex; +use rspack_util::try_any_sync; use swc_css_compiler::{SwcCssCompiler, SwcCssSourceMapGenConfig}; static CSS_ASSET_REGEXP: Lazy = Lazy::new(|| Regex::new(r"\.css(\?.*)?$").expect("Invalid RegExp")); +#[derive(Debug, Default)] +pub struct SwcCssMinimizerRspackPluginOptions { + pub test: Option, + pub include: Option, + pub exclude: Option, +} + #[plugin] #[derive(Debug, Default)] -pub struct SwcCssMinimizerRspackPlugin; +pub struct SwcCssMinimizerRspackPlugin { + options: SwcCssMinimizerRspackPluginOptions, +} + +impl SwcCssMinimizerRspackPlugin { + pub fn new(options: SwcCssMinimizerRspackPluginOptions) -> Self { + Self::new_inner(options) + } +} #[plugin_hook(CompilationProcessAssets for SwcCssMinimizerRspackPlugin, stage = Compilation::PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE)] async fn process_assets(&self, compilation: &mut Compilation) -> Result<()> { + let minify_options = &self.options; + compilation .assets_mut() .par_iter_mut() - .filter(|(filename, _)| CSS_ASSET_REGEXP.is_match(filename)) + .filter(|(filename, original)| { + if !CSS_ASSET_REGEXP.is_match(filename) { + return false; + } + + let is_matched = match_object(minify_options, filename).unwrap_or(false); + + if !is_matched || original.get_info().minimized { + return false; + } + + true + }) .try_for_each(|(filename, original)| -> Result<()> { if original.get_info().minimized { return Ok(()); @@ -69,3 +100,54 @@ impl Plugin for SwcCssMinimizerRspackPlugin { // TODO: chunk hash } + +#[derive(Debug, Clone, Hash)] +pub enum SwcCssMinimizerRule { + String(String), + Regexp(RspackRegex), +} + +impl SwcCssMinimizerRule { + pub fn try_match(&self, data: &str) -> rspack_error::Result { + match self { + Self::String(s) => Ok(data.starts_with(s)), + Self::Regexp(r) => Ok(r.test(data)), + } + } +} + +#[derive(Debug, Clone, Hash)] +pub enum SwcCssMinimizerRules { + String(String), + Regexp(rspack_regex::RspackRegex), + Array(Vec), +} + +impl SwcCssMinimizerRules { + pub fn try_match(&self, data: &str) -> rspack_error::Result { + match self { + Self::String(s) => Ok(data.starts_with(s)), + Self::Regexp(r) => Ok(r.test(data)), + Self::Array(l) => try_any_sync(l, |i| i.try_match(data)), + } + } +} + +pub fn match_object(obj: &SwcCssMinimizerRspackPluginOptions, str: &str) -> Result { + if let Some(condition) = &obj.test { + if !condition.try_match(str)? { + return Ok(false); + } + } + if let Some(condition) = &obj.include { + if !condition.try_match(str)? { + return Ok(false); + } + } + if let Some(condition) = &obj.exclude { + if condition.try_match(str)? { + return Ok(false); + } + } + Ok(true) +} diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.css b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.css new file mode 100644 index 00000000000..e7a19c34ec1 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.css @@ -0,0 +1,3 @@ +html { + margin: 0; +} diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.js b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.js new file mode 100644 index 00000000000..2e77b884a3c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/a.js @@ -0,0 +1 @@ +require("./a.css"); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.css b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.css new file mode 100644 index 00000000000..e7a19c34ec1 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.css @@ -0,0 +1,3 @@ +html { + margin: 0; +} diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.js b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.js new file mode 100644 index 00000000000..4753b41b1c4 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/b.js @@ -0,0 +1 @@ +require("./b.css"); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/index.js b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/index.js new file mode 100644 index 00000000000..596aa318cc3 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/index.js @@ -0,0 +1,12 @@ +const fs = require("fs"); +const path = require("path"); + +it("[minify-exclude-css]: chunk a should be minified", () => { + const content = fs.readFileSync(path.resolve(__dirname, "a.css"), "utf-8"); + expect(content).not.toMatch("\n"); +}); + +it("[minify-exclude-css]: chunk b should not be minified", () => { + const content = fs.readFileSync(path.resolve(__dirname, "b.css"), "utf-8"); + expect(content).toMatch("\n"); +}); diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/rspack.config.js b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/rspack.config.js new file mode 100644 index 00000000000..689ba495e58 --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/rspack.config.js @@ -0,0 +1,29 @@ +const rspack = require("@rspack/core"); +/** + * @type {import("@rspack/core").Configuration} + */ +module.exports = { + entry: { + a: "./a.js", + b: "./b.js", + main: "./index.js" + }, + output: { + filename: "[name].js" + }, + module: { + generator: { + "css/auto": { + exportsOnly: false + } + } + }, + optimization: { + minimize: true, + minimizer: [ + new rspack.SwcCssMinimizerRspackPlugin({ + exclude: [/b\.css/] + }) + ] + } +}; diff --git a/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/test.config.js b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/test.config.js new file mode 100644 index 00000000000..77c3ed9923c --- /dev/null +++ b/packages/rspack-test-tools/tests/configCases/plugins/minify-exclude-css/test.config.js @@ -0,0 +1,6 @@ +/** @type {import("../../../..").TConfigCaseConfig} */ +module.exports = { + findBundle: (i, options) => { + return ["main.js"]; + } +}; diff --git a/packages/rspack/etc/api.md b/packages/rspack/etc/api.md index efc71bf0779..97276b179cd 100644 --- a/packages/rspack/etc/api.md +++ b/packages/rspack/etc/api.md @@ -5004,9 +5004,15 @@ const matchPart: (str: string, test: Matcher) => boolean; // @public (undocumented) type MinifyCondition = string | RegExp; +// @public (undocumented) +type MinifyCondition_2 = string | RegExp; + // @public (undocumented) type MinifyConditions = MinifyCondition | MinifyCondition[]; +// @public (undocumented) +type MinifyConditions_2 = MinifyCondition_2 | MinifyCondition_2[]; + // @public (undocumented) export type Mode = z.infer; @@ -13143,15 +13149,22 @@ const strictModuleExceptionHandling: z.ZodBoolean; // @public (undocumented) export const SwcCssMinimizerRspackPlugin: { - new (options?: any): { + new (options?: SwcCssMinimizerRspackPluginOptions | undefined): { name: BuiltinPluginName; - _args: [options?: any]; + _args: [options?: SwcCssMinimizerRspackPluginOptions | undefined]; affectedHooks: "done" | "compilation" | "failed" | "environment" | "emit" | "make" | "compile" | "afterEmit" | "invalid" | "thisCompilation" | "afterDone" | "normalModuleFactory" | "contextModuleFactory" | "initialize" | "shouldEmit" | "infrastructureLog" | "beforeRun" | "run" | "assetEmitted" | "shutdown" | "watchRun" | "watchClose" | "afterEnvironment" | "afterPlugins" | "afterResolvers" | "beforeCompile" | "afterCompile" | "finishMake" | "entryOption" | undefined; raw(compiler: Compiler_2): BuiltinPlugin; apply(compiler: Compiler_2): void; }; }; +// @public (undocumented) +type SwcCssMinimizerRspackPluginOptions = { + test?: MinifyConditions_2; + exclude?: MinifyConditions_2; + include?: MinifyConditions_2; +}; + // @public (undocumented) export const SwcJsMinimizerRspackPlugin: { new (options?: SwcJsMinimizerRspackPluginOptions | undefined): { diff --git a/packages/rspack/src/builtin-plugin/SwcCssMinimizerPlugin.ts b/packages/rspack/src/builtin-plugin/SwcCssMinimizerPlugin.ts index 75ddb11fb14..1854ef0c4ca 100644 --- a/packages/rspack/src/builtin-plugin/SwcCssMinimizerPlugin.ts +++ b/packages/rspack/src/builtin-plugin/SwcCssMinimizerPlugin.ts @@ -1,8 +1,28 @@ -import { BuiltinPluginName } from "@rspack/binding"; +import { + BuiltinPluginName, + RawSwcCssMinimizerRspackPluginOptions +} from "@rspack/binding"; import { create } from "./base"; +type MinifyCondition = string | RegExp; +type MinifyConditions = MinifyCondition | MinifyCondition[]; + +export type SwcCssMinimizerRspackPluginOptions = { + test?: MinifyConditions; + exclude?: MinifyConditions; + include?: MinifyConditions; +}; + export const SwcCssMinimizerRspackPlugin = create( BuiltinPluginName.SwcCssMinimizerRspackPlugin, - (options?: any /* TODO: extend more options */) => undefined + ( + options?: SwcCssMinimizerRspackPluginOptions + ): RawSwcCssMinimizerRspackPluginOptions => { + return { + test: options?.test, + include: options?.include, + exclude: options?.exclude + }; + } ); diff --git a/website/docs/en/plugins/rspack/swc-css-minimizer-rspack-plugin.mdx b/website/docs/en/plugins/rspack/swc-css-minimizer-rspack-plugin.mdx index bfed9668804..1975c3a01b7 100644 --- a/website/docs/en/plugins/rspack/swc-css-minimizer-rspack-plugin.mdx +++ b/website/docs/en/plugins/rspack/swc-css-minimizer-rspack-plugin.mdx @@ -10,7 +10,22 @@ This plugin can be used to compress CSS assets. See [optimization.minimizer](/co module.exports = { // ... optimization: { - minimizer: [new rspack.SwcCssMinimizerRspackPlugin()], + minimizer: [new rspack.SwcCssMinimizerRspackPlugin(options)], }, }; ``` + +- options + + - **Type:** + + ```ts + type SwcCssMinimizerRspackPluginOptions = { + test?: MinifyConditions; + exclude?: MinifyConditions; + include?: MinifyConditions; + }; + + type MinifyCondition = string | RegExp; + type MinifyConditions = MinifyCondition | MinifyCondition[]; + ```