gulp-purgecss 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.
@@ -26,8 +26,29 @@ declare type ComplexSafelist = {
26
26
  * in the final CSS, even if `child-of-bg` is not found.
27
27
  *
28
28
  */
29
- deep?: RegExp[];
30
- greedy?: RegExp[];
29
+ deep?: StringRegExpArray;
30
+ /**
31
+ * You can safelist whole selectors if any part of that selector matches a
32
+ * pattern with `safelist.greedy`
33
+ *
34
+ * @example
35
+ *
36
+ * ```ts
37
+ * const purgecss = await new PurgeCSS().purge({
38
+ * content: [],
39
+ * css: [],
40
+ * safelist: {
41
+ * greedy: [/red$/]
42
+ * }
43
+ * })
44
+ * ```
45
+ *
46
+ * In this example, selectors such as `button.bg-red.nonexistent-class`
47
+ * will be left in the final CSS, even if `button` and `nonexistent-class`
48
+ * are not found.
49
+ *
50
+ */
51
+ greedy?: StringRegExpArray;
31
52
  variables?: StringRegExpArray;
32
53
  keyframes?: StringRegExpArray;
33
54
  };
@@ -91,6 +112,12 @@ declare interface RawCSS {
91
112
  }
92
113
 
93
114
  /**
115
+ * Strings and regular expressions an option is matched against. A string
116
+ * matches a value exactly, except a string written between slashes, like
117
+ * `"/^btn-/"` or `"/^btn-/i"`, which is parsed as a regular expression. This
118
+ * form lets configuration files that cannot contain regular expressions, such
119
+ * as `postcss.config.json`, use patterns.
120
+ *
94
121
  * @public
95
122
  */
96
123
  declare type StringRegExpArray = Array<RegExp | string>;
@@ -141,7 +168,7 @@ declare interface UserDefinedOptions_2 {
141
168
  /** {@inheritDoc purgecss#Options.skippedContentGlobs} */
142
169
  skippedContentGlobs?: Array<string>;
143
170
  /** {@inheritDoc purgecss#Options.dynamicAttributes} */
144
- dynamicAttributes?: string[];
171
+ dynamicAttributes?: StringRegExpArray;
145
172
  }
146
173
 
147
174
  /**
@@ -1,7 +1,7 @@
1
1
  import glob from 'fast-glob';
2
2
  import PluginError from 'plugin-error';
3
3
  import { PurgeCSS } from 'purgecss';
4
- import through from 'through2';
4
+ import { objectTransform, transform } from 'through2';
5
5
  import applySourceMap from 'vinyl-sourcemaps-apply';
6
6
 
7
7
  const PLUGIN_NAME = "gulp-purgecss";
@@ -10,6 +10,36 @@ function getFiles(contentArray, ignore) {
10
10
  return [...acc, ...glob.sync(content, { ignore })];
11
11
  }, []);
12
12
  }
13
+ // generate a separate source map describing only this step, without any
14
+ // annotation in the css, so that it can be chained with gulp-sourcemaps
15
+ function getSourceMapOptions(file) {
16
+ return file.sourceMap
17
+ ? { inline: false, annotation: false, prev: false }
18
+ : false;
19
+ }
20
+ function applyPurgedSourceMap(file, sourceMap) {
21
+ if (!file.sourceMap || !sourceMap)
22
+ return;
23
+ const map = JSON.parse(sourceMap);
24
+ // the css is purged as a raw string, so the source map has no file name
25
+ map.file = file.relative;
26
+ map.sources = map.sources.map(() => file.relative);
27
+ applySourceMap(file, map);
28
+ }
29
+ function readStream(stream) {
30
+ return new Promise((resolve, reject) => {
31
+ let css = "";
32
+ stream
33
+ .on("data", (buffer) => {
34
+ css += buffer.toString();
35
+ })
36
+ .on("end", () => resolve(css))
37
+ .on("error", reject);
38
+ });
39
+ }
40
+ function toPluginError(e) {
41
+ return e instanceof Error ? new PluginError(PLUGIN_NAME, e.message) : e;
42
+ }
13
43
  /**
14
44
  *
15
45
  * @param options - options
@@ -18,10 +48,13 @@ function getFiles(contentArray, ignore) {
18
48
  * @public
19
49
  */
20
50
  function gulpPurgeCSS(options) {
21
- return through.obj(async function (file, _encoding, callback) {
51
+ // through2 v5 dispatches on the function kind: an async transform receives
52
+ // no callback, pushes its resolved value and turns a rejection into a
53
+ // stream error
54
+ return objectTransform(async function (file) {
22
55
  // empty
23
56
  if (file.isNull())
24
- return callback(null, file);
57
+ return file;
25
58
  // buffer
26
59
  if (file.isBuffer()) {
27
60
  try {
@@ -34,7 +67,7 @@ function gulpPurgeCSS(options) {
34
67
  },
35
68
  ],
36
69
  stdin: true,
37
- sourceMap: !!file.sourceMap,
70
+ sourceMap: getSourceMapOptions(file),
38
71
  };
39
72
  const purgedCSSResults = await new PurgeCSS().purge(optionsGulp);
40
73
  const purge = purgedCSSResults[0];
@@ -43,55 +76,41 @@ function gulpPurgeCSS(options) {
43
76
  : purge.css;
44
77
  file.contents = Buffer.from(result, "utf-8");
45
78
  // apply source map to the chain
46
- if (file.sourceMap) {
47
- applySourceMap(file, purge.sourceMap);
48
- }
49
- callback(null, file);
79
+ applyPurgedSourceMap(file, purge.sourceMap);
80
+ return file;
50
81
  }
51
82
  catch (e) {
52
- if (e instanceof Error) {
53
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
54
- }
83
+ throw toPluginError(e);
55
84
  }
56
85
  }
57
86
  // stream
58
87
  if (file.isStream()) {
59
- let css = "";
60
- file.contents
61
- .on("data", (buffer) => {
62
- css += buffer.toString();
63
- })
64
- .on("end", async () => {
65
- try {
66
- const optionsGulp = {
67
- ...options,
68
- css: [
69
- {
70
- raw: css,
71
- },
72
- ],
73
- sourceMap: !!file.sourceMap,
74
- };
75
- const purgedCSSResults = await new PurgeCSS().purge(optionsGulp);
76
- const purge = purgedCSSResults[0];
77
- const result = optionsGulp.rejected && purge.rejected
78
- ? purge.rejected.join(" {}\n") + " {}"
79
- : purge.css;
80
- const streamResult = through();
81
- streamResult.write(Buffer.from(result, "utf-8"));
82
- file.contents = file.contents.pipe(streamResult);
83
- // apply source map to the chain
84
- if (file.sourceMap) {
85
- applySourceMap(file, purge.sourceMap);
86
- }
87
- callback(null, file);
88
- }
89
- catch (e) {
90
- if (e instanceof Error) {
91
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
92
- }
93
- }
94
- });
88
+ try {
89
+ const css = await readStream(file.contents);
90
+ const optionsGulp = {
91
+ ...options,
92
+ css: [
93
+ {
94
+ raw: css,
95
+ },
96
+ ],
97
+ sourceMap: getSourceMapOptions(file),
98
+ };
99
+ const purgedCSSResults = await new PurgeCSS().purge(optionsGulp);
100
+ const purge = purgedCSSResults[0];
101
+ const result = optionsGulp.rejected && purge.rejected
102
+ ? purge.rejected.join(" {}\n") + " {}"
103
+ : purge.css;
104
+ const streamResult = transform();
105
+ streamResult.write(Buffer.from(result, "utf-8"));
106
+ file.contents = file.contents.pipe(streamResult);
107
+ // apply source map to the chain
108
+ applyPurgedSourceMap(file, purge.sourceMap);
109
+ return file;
110
+ }
111
+ catch (e) {
112
+ throw toPluginError(e);
113
+ }
95
114
  }
96
115
  });
97
116
  }
@@ -3,7 +3,7 @@
3
3
  var glob = require('fast-glob');
4
4
  var PluginError = require('plugin-error');
5
5
  var purgecss = require('purgecss');
6
- var through = require('through2');
6
+ var through2 = require('through2');
7
7
  var applySourceMap = require('vinyl-sourcemaps-apply');
8
8
 
9
9
  const PLUGIN_NAME = "gulp-purgecss";
@@ -12,6 +12,36 @@ function getFiles(contentArray, ignore) {
12
12
  return [...acc, ...glob.sync(content, { ignore })];
13
13
  }, []);
14
14
  }
15
+ // generate a separate source map describing only this step, without any
16
+ // annotation in the css, so that it can be chained with gulp-sourcemaps
17
+ function getSourceMapOptions(file) {
18
+ return file.sourceMap
19
+ ? { inline: false, annotation: false, prev: false }
20
+ : false;
21
+ }
22
+ function applyPurgedSourceMap(file, sourceMap) {
23
+ if (!file.sourceMap || !sourceMap)
24
+ return;
25
+ const map = JSON.parse(sourceMap);
26
+ // the css is purged as a raw string, so the source map has no file name
27
+ map.file = file.relative;
28
+ map.sources = map.sources.map(() => file.relative);
29
+ applySourceMap(file, map);
30
+ }
31
+ function readStream(stream) {
32
+ return new Promise((resolve, reject) => {
33
+ let css = "";
34
+ stream
35
+ .on("data", (buffer) => {
36
+ css += buffer.toString();
37
+ })
38
+ .on("end", () => resolve(css))
39
+ .on("error", reject);
40
+ });
41
+ }
42
+ function toPluginError(e) {
43
+ return e instanceof Error ? new PluginError(PLUGIN_NAME, e.message) : e;
44
+ }
15
45
  /**
16
46
  *
17
47
  * @param options - options
@@ -20,10 +50,13 @@ function getFiles(contentArray, ignore) {
20
50
  * @public
21
51
  */
22
52
  function gulpPurgeCSS(options) {
23
- return through.obj(async function (file, _encoding, callback) {
53
+ // through2 v5 dispatches on the function kind: an async transform receives
54
+ // no callback, pushes its resolved value and turns a rejection into a
55
+ // stream error
56
+ return through2.objectTransform(async function (file) {
24
57
  // empty
25
58
  if (file.isNull())
26
- return callback(null, file);
59
+ return file;
27
60
  // buffer
28
61
  if (file.isBuffer()) {
29
62
  try {
@@ -36,7 +69,7 @@ function gulpPurgeCSS(options) {
36
69
  },
37
70
  ],
38
71
  stdin: true,
39
- sourceMap: !!file.sourceMap,
72
+ sourceMap: getSourceMapOptions(file),
40
73
  };
41
74
  const purgedCSSResults = await new purgecss.PurgeCSS().purge(optionsGulp);
42
75
  const purge = purgedCSSResults[0];
@@ -45,55 +78,41 @@ function gulpPurgeCSS(options) {
45
78
  : purge.css;
46
79
  file.contents = Buffer.from(result, "utf-8");
47
80
  // apply source map to the chain
48
- if (file.sourceMap) {
49
- applySourceMap(file, purge.sourceMap);
50
- }
51
- callback(null, file);
81
+ applyPurgedSourceMap(file, purge.sourceMap);
82
+ return file;
52
83
  }
53
84
  catch (e) {
54
- if (e instanceof Error) {
55
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
56
- }
85
+ throw toPluginError(e);
57
86
  }
58
87
  }
59
88
  // stream
60
89
  if (file.isStream()) {
61
- let css = "";
62
- file.contents
63
- .on("data", (buffer) => {
64
- css += buffer.toString();
65
- })
66
- .on("end", async () => {
67
- try {
68
- const optionsGulp = {
69
- ...options,
70
- css: [
71
- {
72
- raw: css,
73
- },
74
- ],
75
- sourceMap: !!file.sourceMap,
76
- };
77
- const purgedCSSResults = await new purgecss.PurgeCSS().purge(optionsGulp);
78
- const purge = purgedCSSResults[0];
79
- const result = optionsGulp.rejected && purge.rejected
80
- ? purge.rejected.join(" {}\n") + " {}"
81
- : purge.css;
82
- const streamResult = through();
83
- streamResult.write(Buffer.from(result, "utf-8"));
84
- file.contents = file.contents.pipe(streamResult);
85
- // apply source map to the chain
86
- if (file.sourceMap) {
87
- applySourceMap(file, purge.sourceMap);
88
- }
89
- callback(null, file);
90
- }
91
- catch (e) {
92
- if (e instanceof Error) {
93
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
94
- }
95
- }
96
- });
90
+ try {
91
+ const css = await readStream(file.contents);
92
+ const optionsGulp = {
93
+ ...options,
94
+ css: [
95
+ {
96
+ raw: css,
97
+ },
98
+ ],
99
+ sourceMap: getSourceMapOptions(file),
100
+ };
101
+ const purgedCSSResults = await new purgecss.PurgeCSS().purge(optionsGulp);
102
+ const purge = purgedCSSResults[0];
103
+ const result = optionsGulp.rejected && purge.rejected
104
+ ? purge.rejected.join(" {}\n") + " {}"
105
+ : purge.css;
106
+ const streamResult = through2.transform();
107
+ streamResult.write(Buffer.from(result, "utf-8"));
108
+ file.contents = file.contents.pipe(streamResult);
109
+ // apply source map to the chain
110
+ applyPurgedSourceMap(file, purge.sourceMap);
111
+ return file;
112
+ }
113
+ catch (e) {
114
+ throw toPluginError(e);
115
+ }
97
116
  }
98
117
  });
99
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gulp-purgecss",
3
- "version": "8.0.0",
3
+ "version": "9.0.0",
4
4
  "description": "Gulp plugin for purgecss",
5
5
  "author": "Ffloriel",
6
6
  "homepage": "https://purgecss.com",
@@ -17,6 +17,9 @@
17
17
  "uncss"
18
18
  ],
19
19
  "license": "MIT",
20
+ "engines": {
21
+ "node": ">=22.12.0"
22
+ },
20
23
  "main": "lib/gulp-purgecss.js",
21
24
  "module": "./lib/gulp-purgecss.esm.js",
22
25
  "types": "./lib/gulp-purgecss.d.ts",
@@ -31,24 +34,19 @@
31
34
  "type": "git",
32
35
  "url": "git+https://github.com/FullHuman/purgecss.git"
33
36
  },
34
- "scripts": {
35
- "build": "ts-node build.ts",
36
- "test": "jest"
37
- },
38
37
  "dependencies": {
39
- "fast-glob": "^3.3.2",
40
- "plugin-error": "^2.0.0",
41
- "purgecss": "^8.0.0",
42
- "through2": "^4.0.1",
38
+ "fast-glob": "^3.3.3",
39
+ "plugin-error": "^2.0.1",
40
+ "purgecss": "^9.0.0",
41
+ "through2": "^5.0.12",
43
42
  "vinyl-sourcemaps-apply": "^0.2.1"
44
43
  },
45
44
  "devDependencies": {
46
- "@types/event-stream": "^4.0.0",
47
- "@types/glob": "^9.0.0",
48
- "@types/through2": "^2.0.34",
49
- "@types/vinyl": "^2.0.4",
45
+ "@types/event-stream": "^4.0.6",
46
+ "@types/readable-stream": "^4.0.25",
47
+ "@types/vinyl": "^2.0.12",
50
48
  "event-stream": "^4.0.1",
51
- "vinyl": "^3.0.0"
49
+ "vinyl": "^3.0.1"
52
50
  },
53
51
  "bugs": {
54
52
  "url": "https://github.com/FullHuman/purgecss/issues"
@@ -57,5 +55,8 @@
57
55
  "access": "public",
58
56
  "registry": "https://registry.npmjs.org/"
59
57
  },
60
- "gitHead": "9269dbc8b589832bab90d29f5821fc4cf55077d6"
61
- }
58
+ "scripts": {
59
+ "build": "ts-node build.ts",
60
+ "test": "jest"
61
+ }
62
+ }