purgecss-webpack-plugin 8.0.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,7 +78,7 @@ to filter out directories see the glob-all documentation [here](https://www.npmj
78
78
 
79
79
  ### Options
80
80
 
81
- The options available in purgecss [Configuration](https://www.purgecss.com/configuration.html) are also available in the webpack plugin with the exception of css and content.
81
+ The webpack plugin passes the following options of the purgecss [Configuration](https://www.purgecss.com/configuration.html) to PurgeCSS: `defaultExtractor`, `extractors`, `fontFace`, `keyframes`, `variables`, `safelist`, `blocklist`, `dynamicAttributes` and `rejected`. The plugin purges the CSS assets of the compilation and uses `paths` as the content, so it doesn't take the `css` and `content` options. Source maps are generated when the webpack `devtool` option is set. Other options, such as `rejectedCss` or `skippedContentGlobs`, have no effect.
82
82
 
83
83
  - #### paths
84
84
 
@@ -116,6 +116,8 @@ new PurgeCSSPlugin({
116
116
  });
117
117
  ```
118
118
 
119
+ The names are compared with the chunk names (for an entrypoint, its key in `entry`), not with the file names.
120
+
119
121
  - #### safelist
120
122
 
121
123
  Similar as for the `paths` option, you also can define a function for this option:
@@ -32,8 +32,29 @@ export declare type ComplexSafelist = {
32
32
  * in the final CSS, even if `child-of-bg` is not found.
33
33
  *
34
34
  */
35
- deep?: RegExp[];
36
- greedy?: RegExp[];
35
+ deep?: StringRegExpArray;
36
+ /**
37
+ * You can safelist whole selectors if any part of that selector matches a
38
+ * pattern with `safelist.greedy`
39
+ *
40
+ * @example
41
+ *
42
+ * ```ts
43
+ * const purgecss = await new PurgeCSS().purge({
44
+ * content: [],
45
+ * css: [],
46
+ * safelist: {
47
+ * greedy: [/red$/]
48
+ * }
49
+ * })
50
+ * ```
51
+ *
52
+ * In this example, selectors such as `button.bg-red.nonexistent-class`
53
+ * will be left in the final CSS, even if `button` and `nonexistent-class`
54
+ * are not found.
55
+ *
56
+ */
57
+ greedy?: StringRegExpArray;
37
58
  variables?: StringRegExpArray;
38
59
  keyframes?: StringRegExpArray;
39
60
  };
@@ -126,7 +147,7 @@ export declare interface PurgeCSSUserDefinedOptions {
126
147
  /** {@inheritDoc purgecss#Options.skippedContentGlobs} */
127
148
  skippedContentGlobs?: Array<string>;
128
149
  /** {@inheritDoc purgecss#Options.dynamicAttributes} */
129
- dynamicAttributes?: string[];
150
+ dynamicAttributes?: StringRegExpArray;
130
151
  }
131
152
 
132
153
  /**
@@ -158,6 +179,12 @@ export declare interface RawCSS {
158
179
  export declare type SafelistFunction = () => ComplexSafelist;
159
180
 
160
181
  /**
182
+ * Strings and regular expressions an option is matched against. A string
183
+ * matches a value exactly, except a string written between slashes, like
184
+ * `"/^btn-/"` or `"/^btn-/i"`, which is parsed as a regular expression. This
185
+ * form lets configuration files that cannot contain regular expressions, such
186
+ * as `postcss.config.json`, use patterns.
187
+ *
161
188
  * @public
162
189
  */
163
190
  export declare type StringRegExpArray = Array<RegExp | string>;
@@ -169,8 +196,8 @@ export declare type UserDefinedOptions = Omit<PurgeCSSUserDefinedOptions, "css"
169
196
  paths: string[] | PathFunction;
170
197
  moduleExtensions?: string[];
171
198
  verbose?: boolean;
172
- safelist: PurgeCSSUserDefinedOptions["safelist"] | SafelistFunction;
173
- blocklist: PurgeCSSUserDefinedOptions["blocklist"] | BlocklistFunction;
199
+ safelist?: PurgeCSSUserDefinedOptions["safelist"] | SafelistFunction;
200
+ blocklist?: PurgeCSSUserDefinedOptions["blocklist"] | BlocklistFunction;
174
201
  only?: string[];
175
202
  };
176
203
 
@@ -1,5 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { pathToFileURL } from 'url';
3
4
  import { PurgeCSS, defaultOptions } from 'purgecss';
4
5
  import { sources } from 'webpack';
5
6
 
@@ -55,6 +56,7 @@ function getPurgeCSSOptions(pluginOptions, filesToSearch, asset, fileName, sourc
55
56
  variables: options.variables,
56
57
  safelist: options.safelist,
57
58
  blocklist: options.blocklist,
59
+ dynamicAttributes: options.dynamicAttributes,
58
60
  sourceMap: sourceMap ? { inline: false, to: fileName } : false,
59
61
  };
60
62
  }
@@ -72,7 +74,7 @@ function createSource(name, asset, purgeResult, sourceMap) {
72
74
  return new sources.RawSource(purgeResult.css);
73
75
  }
74
76
  const { source, map } = asset.sourceAndMap();
75
- return new sources.SourceMapSource(purgeResult.css, name, purgeResult.sourceMap, source.toString(), map, false);
77
+ return new sources.SourceMapSource(purgeResult.css, name, purgeResult.sourceMap, source.toString(), map !== null && map !== void 0 ? map : undefined, false);
76
78
  }
77
79
  /**
78
80
  * @public
@@ -88,12 +90,23 @@ class PurgeCSSPlugin {
88
90
  initializePlugin(compilation) {
89
91
  compilation.hooks.additionalAssets.tapPromise(pluginName, async () => {
90
92
  let configFileOptions;
91
- try {
92
- const t = path.resolve(process.cwd(), "purgecss.config.js");
93
- configFileOptions = await import(t);
94
- }
95
- catch {
96
- // no config file present
93
+ const t = path.resolve(process.cwd(), "purgecss.config.js");
94
+ // the config file is optional, but an existing one that fails to load
95
+ // (e.g. a syntax error) is reported instead of being silently ignored
96
+ if (fs.existsSync(t)) {
97
+ try {
98
+ const importedConfig = await import(pathToFileURL(t).href);
99
+ // Handle both ES modules (direct export) and CommonJS (default export)
100
+ configFileOptions =
101
+ importedConfig.default && typeof importedConfig.default === "object"
102
+ ? importedConfig.default
103
+ : importedConfig;
104
+ }
105
+ catch (err) {
106
+ throw new Error(`Error loading the config file ${t}: ${err}`, {
107
+ cause: err,
108
+ });
109
+ }
97
110
  }
98
111
  this.options = {
99
112
  ...(configFileOptions ? configFileOptions : {}),
@@ -114,14 +127,14 @@ class PurgeCSSPlugin {
114
127
  return isFileOfTypes(name, [".css"]);
115
128
  });
116
129
  for (const chunk of compilation.chunks) {
117
- const assetsToPurge = assetsFromCompilation.filter(([name]) => {
118
- if (this.options.only) {
119
- if (!this.options.only.some((only) => name.includes(only))) {
120
- return false;
121
- }
122
- }
123
- return chunk.files.has(name);
124
- });
130
+ // `only` lists chunk names, e.g. the entry names. Compare them with the
131
+ // chunk name, not with the asset path: a substring of the path also
132
+ // matches other chunks (`style` in `style_print.css` or `css/style/`)
133
+ if (this.options.only &&
134
+ !(chunk.name && this.options.only.includes(chunk.name))) {
135
+ continue;
136
+ }
137
+ const assetsToPurge = assetsFromCompilation.filter(([name]) => chunk.files.has(name));
125
138
  for (const [name, asset] of assetsToPurge) {
126
139
  const filesToSearch = entryPaths.filter((v) => !styleExtensions.some((ext) => v.endsWith(ext)));
127
140
  const sourceMapEnabled = !!compilation.compiler.options.devtool;
@@ -2,6 +2,7 @@
2
2
 
3
3
  var fs = require('fs');
4
4
  var path = require('path');
5
+ var url = require('url');
5
6
  var purgecss = require('purgecss');
6
7
  var webpack = require('webpack');
7
8
 
@@ -77,6 +78,7 @@ function getPurgeCSSOptions(pluginOptions, filesToSearch, asset, fileName, sourc
77
78
  variables: options.variables,
78
79
  safelist: options.safelist,
79
80
  blocklist: options.blocklist,
81
+ dynamicAttributes: options.dynamicAttributes,
80
82
  sourceMap: sourceMap ? { inline: false, to: fileName } : false,
81
83
  };
82
84
  }
@@ -94,7 +96,7 @@ function createSource(name, asset, purgeResult, sourceMap) {
94
96
  return new webpack.sources.RawSource(purgeResult.css);
95
97
  }
96
98
  const { source, map } = asset.sourceAndMap();
97
- return new webpack.sources.SourceMapSource(purgeResult.css, name, purgeResult.sourceMap, source.toString(), map, false);
99
+ return new webpack.sources.SourceMapSource(purgeResult.css, name, purgeResult.sourceMap, source.toString(), map !== null && map !== void 0 ? map : undefined, false);
98
100
  }
99
101
  /**
100
102
  * @public
@@ -110,12 +112,23 @@ class PurgeCSSPlugin {
110
112
  initializePlugin(compilation) {
111
113
  compilation.hooks.additionalAssets.tapPromise(pluginName, async () => {
112
114
  let configFileOptions;
113
- try {
114
- const t = path__namespace.resolve(process.cwd(), "purgecss.config.js");
115
- configFileOptions = await import(t);
116
- }
117
- catch {
118
- // no config file present
115
+ const t = path__namespace.resolve(process.cwd(), "purgecss.config.js");
116
+ // the config file is optional, but an existing one that fails to load
117
+ // (e.g. a syntax error) is reported instead of being silently ignored
118
+ if (fs__namespace.existsSync(t)) {
119
+ try {
120
+ const importedConfig = await import(url.pathToFileURL(t).href);
121
+ // Handle both ES modules (direct export) and CommonJS (default export)
122
+ configFileOptions =
123
+ importedConfig.default && typeof importedConfig.default === "object"
124
+ ? importedConfig.default
125
+ : importedConfig;
126
+ }
127
+ catch (err) {
128
+ throw new Error(`Error loading the config file ${t}: ${err}`, {
129
+ cause: err,
130
+ });
131
+ }
119
132
  }
120
133
  this.options = {
121
134
  ...(configFileOptions ? configFileOptions : {}),
@@ -136,14 +149,14 @@ class PurgeCSSPlugin {
136
149
  return isFileOfTypes(name, [".css"]);
137
150
  });
138
151
  for (const chunk of compilation.chunks) {
139
- const assetsToPurge = assetsFromCompilation.filter(([name]) => {
140
- if (this.options.only) {
141
- if (!this.options.only.some((only) => name.includes(only))) {
142
- return false;
143
- }
144
- }
145
- return chunk.files.has(name);
146
- });
152
+ // `only` lists chunk names, e.g. the entry names. Compare them with the
153
+ // chunk name, not with the asset path: a substring of the path also
154
+ // matches other chunks (`style` in `style_print.css` or `css/style/`)
155
+ if (this.options.only &&
156
+ !(chunk.name && this.options.only.includes(chunk.name))) {
157
+ continue;
158
+ }
159
+ const assetsToPurge = assetsFromCompilation.filter(([name]) => chunk.files.has(name));
147
160
  for (const [name, asset] of assetsToPurge) {
148
161
  const filesToSearch = entryPaths.filter((v) => !styleExtensions.some((ext) => v.endsWith(ext)));
149
162
  const sourceMapEnabled = !!compilation.compiler.options.devtool;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "purgecss-webpack-plugin",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "PurgeCSS plugin for webpack - Remove unused css",
5
5
  "author": "Ffloriel",
6
6
  "homepage": "https://purgecss.com",
@@ -18,6 +18,9 @@
18
18
  "purify"
19
19
  ],
20
20
  "license": "MIT",
21
+ "engines": {
22
+ "node": ">=22.12.0"
23
+ },
21
24
  "main": "lib/purgecss-webpack-plugin.js",
22
25
  "module": "./lib/purgecss-webpack-plugin.esm.js",
23
26
  "types": "./lib/purgecss-webpack-plugin.d.ts",
@@ -32,28 +35,27 @@
32
35
  "type": "git",
33
36
  "url": "git+https://github.com/FullHuman/purgecss.git"
34
37
  },
35
- "scripts": {
36
- "build": "ts-node build.ts",
37
- "test": "jest"
38
- },
39
38
  "dependencies": {
40
- "purgecss": "^8.0.0",
41
- "webpack": ">=5.95.0"
39
+ "purgecss": "^9.0.0"
42
40
  },
43
41
  "bugs": {
44
42
  "url": "https://github.com/FullHuman/purgecss/issues"
45
43
  },
46
44
  "devDependencies": {
47
- "@types/webpack-sources": "^3.2.3",
48
- "css-loader": "^7.1.2",
49
- "mini-css-extract-plugin": "^2.9.1"
45
+ "css-loader": "^7.1.5",
46
+ "fast-glob": "^3.3.3",
47
+ "mini-css-extract-plugin": "^2.10.2",
48
+ "webpack": "^5.112.2"
50
49
  },
51
50
  "peerDependencies": {
52
- "webpack": ">=5.0.0"
51
+ "webpack": "^5.0.0"
53
52
  },
54
53
  "publishConfig": {
55
54
  "access": "public",
56
55
  "registry": "https://registry.npmjs.org/"
57
56
  },
58
- "gitHead": "9269dbc8b589832bab90d29f5821fc4cf55077d6"
59
- }
57
+ "scripts": {
58
+ "build": "ts-node build.ts",
59
+ "test": "jest"
60
+ }
61
+ }