htmlnano 3.0.0 → 3.2.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.
Files changed (115) hide show
  1. package/README.md +40 -25
  2. package/dist/_modules/collapseAttributeWhitespace.d.mts +41 -5
  3. package/dist/_modules/collapseAttributeWhitespace.d.ts +41 -5
  4. package/dist/_modules/collapseAttributeWhitespace.js +69 -16
  5. package/dist/_modules/collapseAttributeWhitespace.mjs +67 -17
  6. package/dist/_modules/collapseBooleanAttributes.d.mts +36 -3
  7. package/dist/_modules/collapseBooleanAttributes.d.ts +36 -3
  8. package/dist/_modules/collapseBooleanAttributes.js +18 -11
  9. package/dist/_modules/collapseBooleanAttributes.mjs +18 -11
  10. package/dist/_modules/collapseWhitespace.d.mts +36 -3
  11. package/dist/_modules/collapseWhitespace.d.ts +36 -3
  12. package/dist/_modules/collapseWhitespace.js +25 -2
  13. package/dist/_modules/collapseWhitespace.mjs +25 -2
  14. package/dist/_modules/custom.d.mts +36 -3
  15. package/dist/_modules/custom.d.ts +36 -3
  16. package/dist/_modules/deduplicateAttributeValues.d.mts +36 -3
  17. package/dist/_modules/deduplicateAttributeValues.d.ts +36 -3
  18. package/dist/_modules/deduplicateAttributeValues.js +20 -5
  19. package/dist/_modules/deduplicateAttributeValues.mjs +21 -6
  20. package/dist/_modules/example.d.mts +36 -3
  21. package/dist/_modules/example.d.ts +36 -3
  22. package/dist/_modules/mergeScripts.d.mts +36 -3
  23. package/dist/_modules/mergeScripts.d.ts +36 -3
  24. package/dist/_modules/mergeScripts.js +111 -24
  25. package/dist/_modules/mergeScripts.mjs +111 -24
  26. package/dist/_modules/mergeStyles.d.mts +36 -3
  27. package/dist/_modules/mergeStyles.d.ts +36 -3
  28. package/dist/_modules/mergeStyles.js +66 -4
  29. package/dist/_modules/mergeStyles.mjs +66 -4
  30. package/dist/_modules/minifyAttributes.d.mts +95 -0
  31. package/dist/_modules/minifyAttributes.d.ts +95 -0
  32. package/dist/_modules/minifyAttributes.js +159 -0
  33. package/dist/_modules/minifyAttributes.mjs +157 -0
  34. package/dist/_modules/minifyConditionalComments.d.mts +36 -3
  35. package/dist/_modules/minifyConditionalComments.d.ts +36 -3
  36. package/dist/_modules/minifyConditionalComments.js +37 -19
  37. package/dist/_modules/minifyConditionalComments.mjs +37 -19
  38. package/dist/_modules/minifyCss.d.mts +36 -3
  39. package/dist/_modules/minifyCss.d.ts +36 -3
  40. package/dist/_modules/minifyCss.js +13 -27
  41. package/dist/_modules/minifyCss.mjs +14 -28
  42. package/dist/_modules/minifyHtmlTemplate.d.mts +91 -0
  43. package/dist/_modules/minifyHtmlTemplate.d.ts +91 -0
  44. package/dist/_modules/minifyHtmlTemplate.js +231 -0
  45. package/dist/_modules/minifyHtmlTemplate.mjs +228 -0
  46. package/dist/_modules/minifyJs.d.mts +36 -3
  47. package/dist/_modules/minifyJs.d.ts +36 -3
  48. package/dist/_modules/minifyJs.js +106 -5
  49. package/dist/_modules/minifyJs.mjs +107 -6
  50. package/dist/_modules/minifyJson.d.mts +36 -3
  51. package/dist/_modules/minifyJson.d.ts +36 -3
  52. package/dist/_modules/minifyJson.js +8 -11
  53. package/dist/_modules/minifyJson.mjs +8 -11
  54. package/dist/_modules/minifySvg.d.mts +36 -3
  55. package/dist/_modules/minifySvg.d.ts +36 -3
  56. package/dist/_modules/minifySvg.js +35 -4
  57. package/dist/_modules/minifySvg.mjs +35 -4
  58. package/dist/_modules/minifyUrls.d.mts +37 -4
  59. package/dist/_modules/minifyUrls.d.ts +37 -4
  60. package/dist/_modules/minifyUrls.js +52 -27
  61. package/dist/_modules/minifyUrls.mjs +52 -27
  62. package/dist/_modules/normalizeAttributeValues.d.mts +36 -3
  63. package/dist/_modules/normalizeAttributeValues.d.ts +36 -3
  64. package/dist/_modules/normalizeAttributeValues.js +10 -8
  65. package/dist/_modules/normalizeAttributeValues.mjs +10 -8
  66. package/dist/_modules/removeAttributeQuotes.d.mts +40 -4
  67. package/dist/_modules/removeAttributeQuotes.d.ts +40 -4
  68. package/dist/_modules/removeAttributeQuotes.js +9 -4
  69. package/dist/_modules/removeAttributeQuotes.mjs +9 -4
  70. package/dist/_modules/removeComments.d.mts +37 -4
  71. package/dist/_modules/removeComments.d.ts +37 -4
  72. package/dist/_modules/removeComments.js +44 -12
  73. package/dist/_modules/removeComments.mjs +44 -12
  74. package/dist/_modules/removeEmptyAttributes.d.mts +36 -3
  75. package/dist/_modules/removeEmptyAttributes.d.ts +36 -3
  76. package/dist/_modules/removeEmptyAttributes.js +37 -16
  77. package/dist/_modules/removeEmptyAttributes.mjs +37 -16
  78. package/dist/_modules/removeEmptyElements.d.mts +95 -0
  79. package/dist/_modules/removeEmptyElements.d.ts +95 -0
  80. package/dist/_modules/removeEmptyElements.js +90 -0
  81. package/dist/_modules/removeEmptyElements.mjs +88 -0
  82. package/dist/_modules/removeOptionalTags.d.mts +36 -3
  83. package/dist/_modules/removeOptionalTags.d.ts +36 -3
  84. package/dist/_modules/removeOptionalTags.js +39 -28
  85. package/dist/_modules/removeOptionalTags.mjs +39 -28
  86. package/dist/_modules/removeRedundantAttributes.d.mts +36 -3
  87. package/dist/_modules/removeRedundantAttributes.d.ts +36 -3
  88. package/dist/_modules/removeRedundantAttributes.js +43 -28
  89. package/dist/_modules/removeRedundantAttributes.mjs +43 -28
  90. package/dist/_modules/removeUnusedCss.d.mts +37 -3
  91. package/dist/_modules/removeUnusedCss.d.ts +37 -3
  92. package/dist/_modules/removeUnusedCss.js +40 -14
  93. package/dist/_modules/removeUnusedCss.mjs +41 -15
  94. package/dist/_modules/sortAttributes.d.mts +39 -5
  95. package/dist/_modules/sortAttributes.d.ts +39 -5
  96. package/dist/_modules/sortAttributes.js +27 -8
  97. package/dist/_modules/sortAttributes.mjs +27 -8
  98. package/dist/_modules/sortAttributesWithLists.d.mts +39 -4
  99. package/dist/_modules/sortAttributesWithLists.d.ts +39 -4
  100. package/dist/_modules/sortAttributesWithLists.js +61 -52
  101. package/dist/_modules/sortAttributesWithLists.mjs +62 -53
  102. package/dist/helpers.d.ts +8 -1
  103. package/dist/helpers.js +48 -0
  104. package/dist/helpers.mjs +45 -1
  105. package/dist/index.d.ts +37 -4
  106. package/dist/index.js +13 -0
  107. package/dist/index.mjs +13 -0
  108. package/dist/presets/ampSafe.d.ts +36 -3
  109. package/dist/presets/max.d.ts +36 -3
  110. package/dist/presets/max.js +13 -5
  111. package/dist/presets/max.mjs +13 -5
  112. package/dist/presets/safe.d.ts +36 -3
  113. package/dist/presets/safe.js +6 -0
  114. package/dist/presets/safe.mjs +6 -0
  115. package/package.json +27 -15
@@ -1,5 +1,29 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
+ function normalizeAttrValue(value) {
4
+ if (typeof value !== 'string') return null;
5
+ const trimmed = value.trim();
6
+ return trimmed.length === 0 ? '' : trimmed.toLowerCase();
7
+ }
8
+ function findAttrEntry(attrs, name) {
9
+ const targetName = name.toLowerCase();
10
+ for (const [attrName, attrValue] of Object.entries(attrs)){
11
+ if (attrName.toLowerCase() === targetName) {
12
+ return {
13
+ name: attrName,
14
+ value: attrValue
15
+ };
16
+ }
17
+ }
18
+ return null;
19
+ }
20
+ function getNormalizedAttrValue(attrs, name) {
21
+ const entry = findAttrEntry(attrs, name);
22
+ return entry ? normalizeAttrValue(entry.value) : null;
23
+ }
24
+ function hasAttr(attrs, name) {
25
+ return findAttrEntry(attrs, name) !== null;
26
+ }
3
27
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
4
28
  const redundantScriptTypes = new Set([
5
29
  'application/javascript',
@@ -34,22 +58,14 @@ const missingValueDefaultAttributes = {
34
58
  script: {
35
59
  language: 'javascript',
36
60
  type: (attrs)=>{
37
- for (const [attrName, attrValue] of Object.entries(attrs)){
38
- if (attrName.toLowerCase() !== 'type') {
39
- continue;
40
- }
41
- if (typeof attrValue === 'string') {
42
- return redundantScriptTypes.has(attrValue);
43
- }
44
- return false;
45
- }
46
- return false;
61
+ const typeValue = getNormalizedAttrValue(attrs, 'type');
62
+ return typeValue !== null && redundantScriptTypes.has(typeValue);
47
63
  },
48
64
  // Remove attribute if the function returns false
49
65
  charset: (attrs)=>{
50
66
  // The charset attribute only really makes sense on “external” SCRIPT elements:
51
67
  // http://perfectionkills.com/optimizing-html/#8_script_charset
52
- return !attrs.src;
68
+ return !hasAttr(attrs, 'src');
53
69
  }
54
70
  },
55
71
  style: {
@@ -60,19 +76,15 @@ const missingValueDefaultAttributes = {
60
76
  media: 'all',
61
77
  type: (attrs)=>{
62
78
  // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
63
- let isRelStyleSheet = false;
64
- let isTypeTextCSS = false;
65
- if (attrs) {
66
- for (const [attrName, attrValue] of Object.entries(attrs)){
67
- if (attrName.toLowerCase() === 'rel' && attrValue === 'stylesheet') {
68
- isRelStyleSheet = true;
69
- }
70
- if (attrName.toLowerCase() === 'type' && attrValue === 'text/css') {
71
- isTypeTextCSS = true;
72
- }
73
- }
79
+ const relValue = getNormalizedAttrValue(attrs, 'rel');
80
+ const typeValue = getNormalizedAttrValue(attrs, 'type');
81
+ if (!relValue || !typeValue) {
82
+ return false;
74
83
  }
75
- // Only "text/css" is redudant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
84
+ const relTokens = relValue.split(/\s+/);
85
+ const isRelStyleSheet = relTokens.includes('stylesheet');
86
+ const isTypeTextCSS = typeValue === 'text/css';
87
+ // Only "text/css" is redundant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
76
88
  return isRelStyleSheet && isTypeTextCSS;
77
89
  }
78
90
  },
@@ -104,18 +116,21 @@ const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(missingValueDe
104
116
  return (attrs, node)=>{
105
117
  if (!node.tag) return attrs;
106
118
  const newAttrs = attrs;
119
+ const attrsRecord = attrs;
107
120
  if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
108
121
  const tagRedundantAttributes = missingValueDefaultAttributes[node.tag];
109
122
  for (const redundantAttributeName of Object.keys(tagRedundantAttributes)){
110
123
  const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
111
124
  let isRemove = false;
125
+ const attrEntry = findAttrEntry(attrsRecord, redundantAttributeName);
112
126
  if (typeof tagRedundantAttributeValue === 'function') {
113
- isRemove = tagRedundantAttributeValue(attrs);
114
- } else if (attrs[redundantAttributeName] === tagRedundantAttributeValue) {
115
- isRemove = true;
127
+ isRemove = tagRedundantAttributeValue(attrsRecord);
128
+ } else if (attrEntry) {
129
+ const normalizedValue = normalizeAttrValue(attrEntry.value);
130
+ isRemove = normalizedValue !== null && normalizedValue === tagRedundantAttributeValue;
116
131
  }
117
- if (isRemove) {
118
- delete newAttrs[redundantAttributeName];
132
+ if (isRemove && attrEntry) {
133
+ delete newAttrs[attrEntry.name];
119
134
  }
120
135
  }
121
136
  }
@@ -1,3 +1,27 @@
1
+ function normalizeAttrValue(value) {
2
+ if (typeof value !== 'string') return null;
3
+ const trimmed = value.trim();
4
+ return trimmed.length === 0 ? '' : trimmed.toLowerCase();
5
+ }
6
+ function findAttrEntry(attrs, name) {
7
+ const targetName = name.toLowerCase();
8
+ for (const [attrName, attrValue] of Object.entries(attrs)){
9
+ if (attrName.toLowerCase() === targetName) {
10
+ return {
11
+ name: attrName,
12
+ value: attrValue
13
+ };
14
+ }
15
+ }
16
+ return null;
17
+ }
18
+ function getNormalizedAttrValue(attrs, name) {
19
+ const entry = findAttrEntry(attrs, name);
20
+ return entry ? normalizeAttrValue(entry.value) : null;
21
+ }
22
+ function hasAttr(attrs, name) {
23
+ return findAttrEntry(attrs, name) !== null;
24
+ }
1
25
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#JavaScript_types
2
26
  const redundantScriptTypes = new Set([
3
27
  'application/javascript',
@@ -32,22 +56,14 @@ const missingValueDefaultAttributes = {
32
56
  script: {
33
57
  language: 'javascript',
34
58
  type: (attrs)=>{
35
- for (const [attrName, attrValue] of Object.entries(attrs)){
36
- if (attrName.toLowerCase() !== 'type') {
37
- continue;
38
- }
39
- if (typeof attrValue === 'string') {
40
- return redundantScriptTypes.has(attrValue);
41
- }
42
- return false;
43
- }
44
- return false;
59
+ const typeValue = getNormalizedAttrValue(attrs, 'type');
60
+ return typeValue !== null && redundantScriptTypes.has(typeValue);
45
61
  },
46
62
  // Remove attribute if the function returns false
47
63
  charset: (attrs)=>{
48
64
  // The charset attribute only really makes sense on “external” SCRIPT elements:
49
65
  // http://perfectionkills.com/optimizing-html/#8_script_charset
50
- return !attrs.src;
66
+ return !hasAttr(attrs, 'src');
51
67
  }
52
68
  },
53
69
  style: {
@@ -58,19 +74,15 @@ const missingValueDefaultAttributes = {
58
74
  media: 'all',
59
75
  type: (attrs)=>{
60
76
  // https://html.spec.whatwg.org/multipage/links.html#link-type-stylesheet
61
- let isRelStyleSheet = false;
62
- let isTypeTextCSS = false;
63
- if (attrs) {
64
- for (const [attrName, attrValue] of Object.entries(attrs)){
65
- if (attrName.toLowerCase() === 'rel' && attrValue === 'stylesheet') {
66
- isRelStyleSheet = true;
67
- }
68
- if (attrName.toLowerCase() === 'type' && attrValue === 'text/css') {
69
- isTypeTextCSS = true;
70
- }
71
- }
77
+ const relValue = getNormalizedAttrValue(attrs, 'rel');
78
+ const typeValue = getNormalizedAttrValue(attrs, 'type');
79
+ if (!relValue || !typeValue) {
80
+ return false;
72
81
  }
73
- // Only "text/css" is redudant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
82
+ const relTokens = relValue.split(/\s+/);
83
+ const isRelStyleSheet = relTokens.includes('stylesheet');
84
+ const isTypeTextCSS = typeValue === 'text/css';
85
+ // Only "text/css" is redundant for link[rel=stylesheet]. Otherwise "type" shouldn't be removed
74
86
  return isRelStyleSheet && isTypeTextCSS;
75
87
  }
76
88
  },
@@ -102,18 +114,21 @@ const tagsHaveMissingValueDefaultAttributes = new Set(Object.keys(missingValueDe
102
114
  return (attrs, node)=>{
103
115
  if (!node.tag) return attrs;
104
116
  const newAttrs = attrs;
117
+ const attrsRecord = attrs;
105
118
  if (tagsHaveMissingValueDefaultAttributes.has(node.tag)) {
106
119
  const tagRedundantAttributes = missingValueDefaultAttributes[node.tag];
107
120
  for (const redundantAttributeName of Object.keys(tagRedundantAttributes)){
108
121
  const tagRedundantAttributeValue = tagRedundantAttributes[redundantAttributeName];
109
122
  let isRemove = false;
123
+ const attrEntry = findAttrEntry(attrsRecord, redundantAttributeName);
110
124
  if (typeof tagRedundantAttributeValue === 'function') {
111
- isRemove = tagRedundantAttributeValue(attrs);
112
- } else if (attrs[redundantAttributeName] === tagRedundantAttributeValue) {
113
- isRemove = true;
125
+ isRemove = tagRedundantAttributeValue(attrsRecord);
126
+ } else if (attrEntry) {
127
+ const normalizedValue = normalizeAttrValue(attrEntry.value);
128
+ isRemove = normalizedValue !== null && normalizedValue === tagRedundantAttributeValue;
114
129
  }
115
- if (isRemove) {
116
- delete newAttrs[redundantAttributeName];
130
+ if (isRemove && attrEntry) {
131
+ delete newAttrs[attrEntry.name];
117
132
  }
118
133
  }
119
134
  }
@@ -2,16 +2,24 @@ import PostHTML from 'posthtml';
2
2
  import { MinifyOptions } from 'terser';
3
3
  import { Options } from 'cssnano';
4
4
  import { Config } from 'svgo';
5
+ import { UserDefinedOptions } from 'purgecss';
5
6
 
6
7
  type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
7
8
  options?: {
8
9
  quoteAllAttributes?: boolean | undefined;
10
+ quoteStyle?: 0 | 1 | 2 | undefined;
11
+ replaceQuote?: boolean | undefined;
9
12
  } | undefined;
10
13
  render(): string;
11
14
  render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
12
15
  };
13
16
  type MaybeArray<T> = T | Array<T>;
14
17
  type PostHTMLNodeLike = PostHTML.Node | string;
18
+ type HtmlnanoTemplateRule = {
19
+ tag: string;
20
+ attrs?: Record<string, string | boolean | void>;
21
+ };
22
+ type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
15
23
  interface HtmlnanoOptions {
16
24
  skipConfigLoading?: boolean;
17
25
  configPath?: string;
@@ -27,17 +35,42 @@ interface HtmlnanoOptions {
27
35
  mergeStyles?: boolean;
28
36
  mergeScripts?: boolean;
29
37
  minifyCss?: Options | boolean;
38
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
30
39
  minifyConditionalComments?: boolean;
31
40
  minifyJs?: MinifyOptions | boolean;
32
41
  minifyJson?: boolean;
42
+ minifyAttributes?: boolean | {
43
+ metaContent?: boolean;
44
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
45
+ };
33
46
  minifySvg?: Config | boolean;
34
47
  normalizeAttributeValues?: boolean;
35
- removeAttributeQuotes?: boolean;
36
- removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
48
+ removeAttributeQuotes?: boolean | {
49
+ force?: boolean;
50
+ };
51
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
37
52
  removeEmptyAttributes?: boolean;
53
+ removeEmptyElements?: boolean | {
54
+ removeWithAttributes?: boolean;
55
+ };
38
56
  removeRedundantAttributes?: boolean;
39
57
  removeOptionalTags?: boolean;
40
- removeUnusedCss?: boolean;
58
+ removeUnusedCss?: boolean | ({
59
+ tool: 'purgeCSS';
60
+ } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
61
+ banner?: boolean;
62
+ csspath?: string;
63
+ htmlroot?: string;
64
+ ignore?: (string | RegExp)[];
65
+ inject?: string;
66
+ jsdom?: object;
67
+ media?: string[];
68
+ report?: boolean;
69
+ strictSSL?: boolean;
70
+ timeout?: number;
71
+ uncssrc?: string;
72
+ userAgent?: string;
73
+ };
41
74
  sortAttributes?: boolean | 'alphabetical' | 'frequency';
42
75
  sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
43
76
  }
@@ -54,6 +87,7 @@ type HtmlnanoModule<Options = any> = {
54
87
 
55
88
  interface RemoveUnusedCssOptions {
56
89
  tool?: 'purgeCSS' | 'uncss';
90
+ [key: string]: unknown;
57
91
  }
58
92
  declare const mod: HtmlnanoModule<RemoveUnusedCssOptions>;
59
93
 
@@ -2,16 +2,24 @@ import PostHTML from 'posthtml';
2
2
  import { MinifyOptions } from 'terser';
3
3
  import { Options } from 'cssnano';
4
4
  import { Config } from 'svgo';
5
+ import { UserDefinedOptions } from 'purgecss';
5
6
 
6
7
  type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
7
8
  options?: {
8
9
  quoteAllAttributes?: boolean | undefined;
10
+ quoteStyle?: 0 | 1 | 2 | undefined;
11
+ replaceQuote?: boolean | undefined;
9
12
  } | undefined;
10
13
  render(): string;
11
14
  render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
12
15
  };
13
16
  type MaybeArray<T> = T | Array<T>;
14
17
  type PostHTMLNodeLike = PostHTML.Node | string;
18
+ type HtmlnanoTemplateRule = {
19
+ tag: string;
20
+ attrs?: Record<string, string | boolean | void>;
21
+ };
22
+ type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
15
23
  interface HtmlnanoOptions {
16
24
  skipConfigLoading?: boolean;
17
25
  configPath?: string;
@@ -27,17 +35,42 @@ interface HtmlnanoOptions {
27
35
  mergeStyles?: boolean;
28
36
  mergeScripts?: boolean;
29
37
  minifyCss?: Options | boolean;
38
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
30
39
  minifyConditionalComments?: boolean;
31
40
  minifyJs?: MinifyOptions | boolean;
32
41
  minifyJson?: boolean;
42
+ minifyAttributes?: boolean | {
43
+ metaContent?: boolean;
44
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
45
+ };
33
46
  minifySvg?: Config | boolean;
34
47
  normalizeAttributeValues?: boolean;
35
- removeAttributeQuotes?: boolean;
36
- removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
48
+ removeAttributeQuotes?: boolean | {
49
+ force?: boolean;
50
+ };
51
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
37
52
  removeEmptyAttributes?: boolean;
53
+ removeEmptyElements?: boolean | {
54
+ removeWithAttributes?: boolean;
55
+ };
38
56
  removeRedundantAttributes?: boolean;
39
57
  removeOptionalTags?: boolean;
40
- removeUnusedCss?: boolean;
58
+ removeUnusedCss?: boolean | ({
59
+ tool: 'purgeCSS';
60
+ } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
61
+ banner?: boolean;
62
+ csspath?: string;
63
+ htmlroot?: string;
64
+ ignore?: (string | RegExp)[];
65
+ inject?: string;
66
+ jsdom?: object;
67
+ media?: string[];
68
+ report?: boolean;
69
+ strictSSL?: boolean;
70
+ timeout?: number;
71
+ uncssrc?: string;
72
+ userAgent?: string;
73
+ };
41
74
  sortAttributes?: boolean | 'alphabetical' | 'frequency';
42
75
  sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
43
76
  }
@@ -54,6 +87,7 @@ type HtmlnanoModule<Options = any> = {
54
87
 
55
88
  interface RemoveUnusedCssOptions {
56
89
  tool?: 'purgeCSS' | 'uncss';
90
+ [key: string]: unknown;
57
91
  }
58
92
  declare const mod: HtmlnanoModule<RemoveUnusedCssOptions>;
59
93
 
@@ -9,10 +9,12 @@ const uncssOptions = {
9
9
  ],
10
10
  stylesheets: []
11
11
  };
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss has no types
12
13
  function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
13
14
  const css = helpers_js.extractCssFromStyleNode(styleNode);
15
+ const { strippedCss, isCdataWrapped } = helpers_js.stripCssCdata(css);
14
16
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- uncss no types
15
- return runUncss(html, css, uncssOptions, uncss).then((css)=>{
17
+ return runUncss(html, strippedCss, uncssOptions, uncss).then((css)=>{
16
18
  // uncss may have left some style tags empty
17
19
  if (css.trim().length === 0) {
18
20
  // @ts-expect-error -- explicitly remove the tag
@@ -21,10 +23,11 @@ function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
21
23
  return;
22
24
  }
23
25
  styleNode.content = [
24
- css
26
+ helpers_js.wrapCssCdata(css, isCdataWrapped)
25
27
  ];
26
28
  });
27
29
  }
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss callback uses untyped args
28
31
  function runUncss(html, css, userOptions, uncss) {
29
32
  if (typeof userOptions !== 'object') {
30
33
  userOptions = {};
@@ -50,8 +53,8 @@ const purgeFromHtml = function(tree) {
50
53
  // making the process faster
51
54
  const selectors = [];
52
55
  tree.walk((node)=>{
53
- const classes = node.attrs && node.attrs.class && node.attrs.class.split(' ') || [];
54
- const ids = node.attrs && node.attrs.id && node.attrs.id.split(' ') || [];
56
+ const classes = getSelectorTokens(node.attrs && node.attrs.class);
57
+ const ids = getSelectorTokens(node.attrs && node.attrs.id);
55
58
  selectors.push(...classes, ...ids);
56
59
  if (node.tag) {
57
60
  selectors.push(node.tag);
@@ -60,9 +63,10 @@ const purgeFromHtml = function(tree) {
60
63
  });
61
64
  return ()=>selectors;
62
65
  };
63
- function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
66
+ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss, extractor) {
64
67
  const css = helpers_js.extractCssFromStyleNode(styleNode);
65
- return runPurgecss(tree, css, purgecssOptions, purgecss).then((css)=>{
68
+ const { strippedCss, isCdataWrapped } = helpers_js.stripCssCdata(css);
69
+ return runPurgecss(tree, strippedCss, purgecssOptions, purgecss, extractor).then((css)=>{
66
70
  if (css.trim().length === 0) {
67
71
  // @ts-expect-error -- explicitly remove the tag
68
72
  styleNode.tag = false;
@@ -70,11 +74,11 @@ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
70
74
  return;
71
75
  }
72
76
  styleNode.content = [
73
- css
77
+ helpers_js.wrapCssCdata(css, isCdataWrapped)
74
78
  ];
75
79
  });
76
80
  }
77
- function runPurgecss(tree, css, userOptions, purgecss) {
81
+ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
78
82
  if (typeof userOptions !== 'object') {
79
83
  userOptions = {};
80
84
  }
@@ -95,7 +99,7 @@ function runPurgecss(tree, css, userOptions, purgecss) {
95
99
  ],
96
100
  extractors: [
97
101
  {
98
- extractor: purgeFromHtml(tree),
102
+ extractor,
99
103
  extensions: [
100
104
  'html'
101
105
  ]
@@ -108,20 +112,26 @@ function runPurgecss(tree, css, userOptions, purgecss) {
108
112
  }
109
113
  /** Remove unused CSS */ const mod = {
110
114
  async default (tree, options, userOptions) {
115
+ var _resolvedOptions_tool;
111
116
  const promises = [];
112
117
  let html;
118
+ let extractor;
113
119
  const purgecss = await helpers_js.optionalImport('purgecss');
114
120
  const uncss = await helpers_js.optionalImport('uncss');
121
+ const resolvedOptions = resolveUserOptions(userOptions);
122
+ const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
123
+ const toolOptions = stripToolOption(resolvedOptions);
115
124
  tree.walk((node)=>{
116
- if (helpers_js.isStyleNode(node)) {
117
- if (userOptions.tool === 'purgeCSS') {
125
+ if (helpers_js.isStyleNode(node) && helpers_js.isCssStyleType(node)) {
126
+ if (tool === 'purgeCSS') {
118
127
  if (purgecss) {
119
- promises.push(processStyleNodePurgeCSS(tree, node, userOptions, purgecss));
128
+ extractor != null ? extractor : extractor = purgeFromHtml(tree);
129
+ promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
120
130
  }
121
- } else {
131
+ } else if (tool === 'uncss') {
122
132
  if (uncss) {
123
133
  html != null ? html : html = tree.render(tree);
124
- promises.push(processStyleNodeUnCSS(html, node, userOptions, uncss));
134
+ promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
125
135
  }
126
136
  }
127
137
  }
@@ -130,5 +140,21 @@ function runPurgecss(tree, css, userOptions, purgecss) {
130
140
  return Promise.all(promises).then(()=>tree);
131
141
  }
132
142
  };
143
+ function getSelectorTokens(value) {
144
+ if (typeof value !== 'string') {
145
+ return [];
146
+ }
147
+ return value.split(/\s+/).filter(Boolean);
148
+ }
149
+ function resolveUserOptions(userOptions) {
150
+ if (userOptions && typeof userOptions === 'object') {
151
+ return userOptions;
152
+ }
153
+ return {};
154
+ }
155
+ function stripToolOption(options) {
156
+ const { tool: _tool, ...rest } = options;
157
+ return rest;
158
+ }
133
159
 
134
160
  exports.default = mod;
@@ -1,4 +1,4 @@
1
- import { optionalImport, isStyleNode, extractCssFromStyleNode } from '../helpers.mjs';
1
+ import { optionalImport, isStyleNode, isCssStyleType, extractCssFromStyleNode, stripCssCdata, wrapCssCdata } from '../helpers.mjs';
2
2
 
3
3
  // These options must be set and shouldn't be overriden to ensure uncss doesn't look at linked stylesheets.
4
4
  const uncssOptions = {
@@ -7,10 +7,12 @@ const uncssOptions = {
7
7
  ],
8
8
  stylesheets: []
9
9
  };
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss has no types
10
11
  function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
11
12
  const css = extractCssFromStyleNode(styleNode);
13
+ const { strippedCss, isCdataWrapped } = stripCssCdata(css);
12
14
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument -- uncss no types
13
- return runUncss(html, css, uncssOptions, uncss).then((css)=>{
15
+ return runUncss(html, strippedCss, uncssOptions, uncss).then((css)=>{
14
16
  // uncss may have left some style tags empty
15
17
  if (css.trim().length === 0) {
16
18
  // @ts-expect-error -- explicitly remove the tag
@@ -19,10 +21,11 @@ function processStyleNodeUnCSS(html, styleNode, uncssOptions, uncss) {
19
21
  return;
20
22
  }
21
23
  styleNode.content = [
22
- css
24
+ wrapCssCdata(css, isCdataWrapped)
23
25
  ];
24
26
  });
25
27
  }
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- uncss callback uses untyped args
26
29
  function runUncss(html, css, userOptions, uncss) {
27
30
  if (typeof userOptions !== 'object') {
28
31
  userOptions = {};
@@ -48,8 +51,8 @@ const purgeFromHtml = function(tree) {
48
51
  // making the process faster
49
52
  const selectors = [];
50
53
  tree.walk((node)=>{
51
- const classes = node.attrs && node.attrs.class && node.attrs.class.split(' ') || [];
52
- const ids = node.attrs && node.attrs.id && node.attrs.id.split(' ') || [];
54
+ const classes = getSelectorTokens(node.attrs && node.attrs.class);
55
+ const ids = getSelectorTokens(node.attrs && node.attrs.id);
53
56
  selectors.push(...classes, ...ids);
54
57
  if (node.tag) {
55
58
  selectors.push(node.tag);
@@ -58,9 +61,10 @@ const purgeFromHtml = function(tree) {
58
61
  });
59
62
  return ()=>selectors;
60
63
  };
61
- function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
64
+ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss, extractor) {
62
65
  const css = extractCssFromStyleNode(styleNode);
63
- return runPurgecss(tree, css, purgecssOptions, purgecss).then((css)=>{
66
+ const { strippedCss, isCdataWrapped } = stripCssCdata(css);
67
+ return runPurgecss(tree, strippedCss, purgecssOptions, purgecss, extractor).then((css)=>{
64
68
  if (css.trim().length === 0) {
65
69
  // @ts-expect-error -- explicitly remove the tag
66
70
  styleNode.tag = false;
@@ -68,11 +72,11 @@ function processStyleNodePurgeCSS(tree, styleNode, purgecssOptions, purgecss) {
68
72
  return;
69
73
  }
70
74
  styleNode.content = [
71
- css
75
+ wrapCssCdata(css, isCdataWrapped)
72
76
  ];
73
77
  });
74
78
  }
75
- function runPurgecss(tree, css, userOptions, purgecss) {
79
+ function runPurgecss(tree, css, userOptions, purgecss, extractor) {
76
80
  if (typeof userOptions !== 'object') {
77
81
  userOptions = {};
78
82
  }
@@ -93,7 +97,7 @@ function runPurgecss(tree, css, userOptions, purgecss) {
93
97
  ],
94
98
  extractors: [
95
99
  {
96
- extractor: purgeFromHtml(tree),
100
+ extractor,
97
101
  extensions: [
98
102
  'html'
99
103
  ]
@@ -106,20 +110,26 @@ function runPurgecss(tree, css, userOptions, purgecss) {
106
110
  }
107
111
  /** Remove unused CSS */ const mod = {
108
112
  async default (tree, options, userOptions) {
113
+ var _resolvedOptions_tool;
109
114
  const promises = [];
110
115
  let html;
116
+ let extractor;
111
117
  const purgecss = await optionalImport('purgecss');
112
118
  const uncss = await optionalImport('uncss');
119
+ const resolvedOptions = resolveUserOptions(userOptions);
120
+ const tool = (_resolvedOptions_tool = resolvedOptions.tool) != null ? _resolvedOptions_tool : 'purgeCSS';
121
+ const toolOptions = stripToolOption(resolvedOptions);
113
122
  tree.walk((node)=>{
114
- if (isStyleNode(node)) {
115
- if (userOptions.tool === 'purgeCSS') {
123
+ if (isStyleNode(node) && isCssStyleType(node)) {
124
+ if (tool === 'purgeCSS') {
116
125
  if (purgecss) {
117
- promises.push(processStyleNodePurgeCSS(tree, node, userOptions, purgecss));
126
+ extractor != null ? extractor : extractor = purgeFromHtml(tree);
127
+ promises.push(processStyleNodePurgeCSS(tree, node, toolOptions, purgecss, extractor));
118
128
  }
119
- } else {
129
+ } else if (tool === 'uncss') {
120
130
  if (uncss) {
121
131
  html != null ? html : html = tree.render(tree);
122
- promises.push(processStyleNodeUnCSS(html, node, userOptions, uncss));
132
+ promises.push(processStyleNodeUnCSS(html, node, toolOptions, uncss));
123
133
  }
124
134
  }
125
135
  }
@@ -128,5 +138,21 @@ function runPurgecss(tree, css, userOptions, purgecss) {
128
138
  return Promise.all(promises).then(()=>tree);
129
139
  }
130
140
  };
141
+ function getSelectorTokens(value) {
142
+ if (typeof value !== 'string') {
143
+ return [];
144
+ }
145
+ return value.split(/\s+/).filter(Boolean);
146
+ }
147
+ function resolveUserOptions(userOptions) {
148
+ if (userOptions && typeof userOptions === 'object') {
149
+ return userOptions;
150
+ }
151
+ return {};
152
+ }
153
+ function stripToolOption(options) {
154
+ const { tool: _tool, ...rest } = options;
155
+ return rest;
156
+ }
131
157
 
132
158
  export { mod as default };