gulp-purgecss 7.0.2 → 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
- import * as glob from 'glob';
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
  }
@@ -1,36 +1,47 @@
1
1
  'use strict';
2
2
 
3
- var glob = require('glob');
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
- function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
20
- });
21
- }
22
- n.default = e;
23
- return Object.freeze(n);
24
- }
25
-
26
- var glob__namespace = /*#__PURE__*/_interopNamespaceDefault(glob);
27
-
28
9
  const PLUGIN_NAME = "gulp-purgecss";
29
10
  function getFiles(contentArray, ignore) {
30
11
  return contentArray.reduce((acc, content) => {
31
- return [...acc, ...glob__namespace.sync(content, { ignore })];
12
+ return [...acc, ...glob.sync(content, { ignore })];
32
13
  }, []);
33
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
+ }
34
45
  /**
35
46
  *
36
47
  * @param options - options
@@ -39,10 +50,13 @@ function getFiles(contentArray, ignore) {
39
50
  * @public
40
51
  */
41
52
  function gulpPurgeCSS(options) {
42
- 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) {
43
57
  // empty
44
58
  if (file.isNull())
45
- return callback(null, file);
59
+ return file;
46
60
  // buffer
47
61
  if (file.isBuffer()) {
48
62
  try {
@@ -55,7 +69,7 @@ function gulpPurgeCSS(options) {
55
69
  },
56
70
  ],
57
71
  stdin: true,
58
- sourceMap: !!file.sourceMap,
72
+ sourceMap: getSourceMapOptions(file),
59
73
  };
60
74
  const purgedCSSResults = await new purgecss.PurgeCSS().purge(optionsGulp);
61
75
  const purge = purgedCSSResults[0];
@@ -64,55 +78,41 @@ function gulpPurgeCSS(options) {
64
78
  : purge.css;
65
79
  file.contents = Buffer.from(result, "utf-8");
66
80
  // apply source map to the chain
67
- if (file.sourceMap) {
68
- applySourceMap(file, purge.sourceMap);
69
- }
70
- callback(null, file);
81
+ applyPurgedSourceMap(file, purge.sourceMap);
82
+ return file;
71
83
  }
72
84
  catch (e) {
73
- if (e instanceof Error) {
74
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
75
- }
85
+ throw toPluginError(e);
76
86
  }
77
87
  }
78
88
  // stream
79
89
  if (file.isStream()) {
80
- let css = "";
81
- file.contents
82
- .on("data", (buffer) => {
83
- css += buffer.toString();
84
- })
85
- .on("end", async () => {
86
- try {
87
- const optionsGulp = {
88
- ...options,
89
- css: [
90
- {
91
- raw: css,
92
- },
93
- ],
94
- sourceMap: !!file.sourceMap,
95
- };
96
- const purgedCSSResults = await new purgecss.PurgeCSS().purge(optionsGulp);
97
- const purge = purgedCSSResults[0];
98
- const result = optionsGulp.rejected && purge.rejected
99
- ? purge.rejected.join(" {}\n") + " {}"
100
- : purge.css;
101
- const streamResult = through();
102
- streamResult.write(Buffer.from(result, "utf-8"));
103
- file.contents = file.contents.pipe(streamResult);
104
- // apply source map to the chain
105
- if (file.sourceMap) {
106
- applySourceMap(file, purge.sourceMap);
107
- }
108
- callback(null, file);
109
- }
110
- catch (e) {
111
- if (e instanceof Error) {
112
- this.emit("error", new PluginError(PLUGIN_NAME, e.message));
113
- }
114
- }
115
- });
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
+ }
116
116
  }
117
117
  });
118
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gulp-purgecss",
3
- "version": "7.0.2",
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
- "glob": "^11.0.0",
40
- "plugin-error": "^2.0.0",
41
- "purgecss": "^7.0.2",
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": "^8.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": "e95398d961e03a809b5e3da75e3dae1813b46b68"
61
- }
58
+ "scripts": {
59
+ "build": "ts-node build.ts",
60
+ "test": "jest"
61
+ }
62
+ }