htmlnano 3.0.0 → 3.1.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 +18 -17
  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 +99 -24
  25. package/dist/_modules/mergeScripts.mjs +99 -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 +56 -4
  29. package/dist/_modules/mergeStyles.mjs +56 -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 +94 -5
  49. package/dist/_modules/minifyJs.mjs +95 -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 +38 -13
  93. package/dist/_modules/removeUnusedCss.mjs +39 -14
  94. package/dist/_modules/sortAttributes.d.mts +36 -3
  95. package/dist/_modules/sortAttributes.d.ts +36 -3
  96. package/dist/_modules/sortAttributes.js +23 -5
  97. package/dist/_modules/sortAttributes.mjs +23 -5
  98. package/dist/_modules/sortAttributesWithLists.d.mts +36 -3
  99. package/dist/_modules/sortAttributesWithLists.d.ts +36 -3
  100. package/dist/_modules/sortAttributesWithLists.js +30 -8
  101. package/dist/_modules/sortAttributesWithLists.mjs +31 -9
  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 +4 -0
  111. package/dist/presets/max.mjs +4 -0
  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 +21 -13
@@ -15,6 +15,9 @@ const processModuleOptions = (options)=>{
15
15
  class AttributeTokenChain {
16
16
  addFromNodeAttrsArray(attrValuesArray) {
17
17
  attrValuesArray.forEach((attrValue)=>{
18
+ if (!attrValue) {
19
+ return;
20
+ }
18
21
  if (this.freqData.has(attrValue)) {
19
22
  this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
20
23
  } else {
@@ -26,16 +29,28 @@ class AttributeTokenChain {
26
29
  const _sortOrder = [
27
30
  ...this.freqData.entries()
28
31
  ];
29
- _sortOrder.sort((a, b)=>b[1] - a[1]);
32
+ _sortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
30
33
  this.sortOrder = _sortOrder.map((i)=>i[0]);
31
34
  }
32
35
  sortFromNodeAttrsArray(attrValuesArray) {
33
36
  const resultArray = [];
37
+ const tokenCounts = new Map();
38
+ attrValuesArray.forEach((attrValue)=>{
39
+ var _tokenCounts_get;
40
+ if (!attrValue) {
41
+ return;
42
+ }
43
+ tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
44
+ });
34
45
  if (!this.sortOrder) {
35
46
  this.createSortOrder();
36
47
  }
37
48
  this.sortOrder.forEach((k)=>{
38
- if (attrValuesArray.includes(k)) {
49
+ const count = tokenCounts.get(k);
50
+ if (!count) {
51
+ return;
52
+ }
53
+ for(let i = 0; i < count; i += 1){
39
54
  resultArray.push(k);
40
55
  }
41
56
  });
@@ -59,17 +74,22 @@ class AttributeTokenChain {
59
74
  return tree;
60
75
  }
61
76
  };
77
+ const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
62
78
  function sortAttributesWithListsInAlphabeticalOrder(tree) {
63
79
  tree.walk((node)=>{
64
80
  if (!node.attrs) {
65
81
  return node;
66
82
  }
83
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
67
84
  Object.keys(node.attrs).forEach((attrName)=>{
68
85
  const attrNameLower = attrName.toLowerCase();
69
- if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrNameLower)) {
86
+ if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
87
+ return;
88
+ }
89
+ const attrValues = splitListAttributeValues(node.attrs[attrName]);
90
+ if (attrValues.length < 2) {
70
91
  return;
71
92
  }
72
- const attrValues = node.attrs[attrName].split(/\s/);
73
93
  node.attrs[attrName] = attrValues.sort((a, b)=>{
74
94
  // @ts-expect-error -- deliberately use minus operator to sort things
75
95
  return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
@@ -86,13 +106,14 @@ function sortAttributesWithListsByFrequency(tree) {
86
106
  if (!node.attrs) {
87
107
  return node;
88
108
  }
109
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
89
110
  Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
90
111
  const attrNameLower = attrName.toLowerCase();
91
- if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrNameLower)) {
112
+ if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
92
113
  return;
93
114
  }
94
115
  tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
95
- tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
116
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
96
117
  });
97
118
  return node;
98
119
  });
@@ -101,13 +122,14 @@ function sortAttributesWithListsByFrequency(tree) {
101
122
  if (!node.attrs) {
102
123
  return node;
103
124
  }
125
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
104
126
  Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
105
127
  const attrNameLower = attrName.toLowerCase();
106
- if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrNameLower)) {
128
+ if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
107
129
  return;
108
130
  }
109
131
  if (tokenChainObj[attrNameLower]) {
110
- node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
132
+ node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
111
133
  }
112
134
  });
113
135
  return node;
@@ -1,4 +1,4 @@
1
- import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
1
+ import { isListAttribute } from './collapseAttributeWhitespace.mjs';
2
2
 
3
3
  // class, rel, ping
4
4
  const validOptions = new Set([
@@ -13,6 +13,9 @@ const processModuleOptions = (options)=>{
13
13
  class AttributeTokenChain {
14
14
  addFromNodeAttrsArray(attrValuesArray) {
15
15
  attrValuesArray.forEach((attrValue)=>{
16
+ if (!attrValue) {
17
+ return;
18
+ }
16
19
  if (this.freqData.has(attrValue)) {
17
20
  this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
18
21
  } else {
@@ -24,16 +27,28 @@ class AttributeTokenChain {
24
27
  const _sortOrder = [
25
28
  ...this.freqData.entries()
26
29
  ];
27
- _sortOrder.sort((a, b)=>b[1] - a[1]);
30
+ _sortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
28
31
  this.sortOrder = _sortOrder.map((i)=>i[0]);
29
32
  }
30
33
  sortFromNodeAttrsArray(attrValuesArray) {
31
34
  const resultArray = [];
35
+ const tokenCounts = new Map();
36
+ attrValuesArray.forEach((attrValue)=>{
37
+ var _tokenCounts_get;
38
+ if (!attrValue) {
39
+ return;
40
+ }
41
+ tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
42
+ });
32
43
  if (!this.sortOrder) {
33
44
  this.createSortOrder();
34
45
  }
35
46
  this.sortOrder.forEach((k)=>{
36
- if (attrValuesArray.includes(k)) {
47
+ const count = tokenCounts.get(k);
48
+ if (!count) {
49
+ return;
50
+ }
51
+ for(let i = 0; i < count; i += 1){
37
52
  resultArray.push(k);
38
53
  }
39
54
  });
@@ -57,17 +72,22 @@ class AttributeTokenChain {
57
72
  return tree;
58
73
  }
59
74
  };
75
+ const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
60
76
  function sortAttributesWithListsInAlphabeticalOrder(tree) {
61
77
  tree.walk((node)=>{
62
78
  if (!node.attrs) {
63
79
  return node;
64
80
  }
81
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
65
82
  Object.keys(node.attrs).forEach((attrName)=>{
66
83
  const attrNameLower = attrName.toLowerCase();
67
- if (!attributesWithLists.has(attrNameLower)) {
84
+ if (!isListAttribute(attrNameLower, tagName)) {
85
+ return;
86
+ }
87
+ const attrValues = splitListAttributeValues(node.attrs[attrName]);
88
+ if (attrValues.length < 2) {
68
89
  return;
69
90
  }
70
- const attrValues = node.attrs[attrName].split(/\s/);
71
91
  node.attrs[attrName] = attrValues.sort((a, b)=>{
72
92
  // @ts-expect-error -- deliberately use minus operator to sort things
73
93
  return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
@@ -84,13 +104,14 @@ function sortAttributesWithListsByFrequency(tree) {
84
104
  if (!node.attrs) {
85
105
  return node;
86
106
  }
107
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
87
108
  Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
88
109
  const attrNameLower = attrName.toLowerCase();
89
- if (!attributesWithLists.has(attrNameLower)) {
110
+ if (!isListAttribute(attrNameLower, tagName)) {
90
111
  return;
91
112
  }
92
113
  tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
93
- tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
114
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
94
115
  });
95
116
  return node;
96
117
  });
@@ -99,13 +120,14 @@ function sortAttributesWithListsByFrequency(tree) {
99
120
  if (!node.attrs) {
100
121
  return node;
101
122
  }
123
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
102
124
  Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
103
125
  const attrNameLower = attrName.toLowerCase();
104
- if (!attributesWithLists.has(attrNameLower)) {
126
+ if (!isListAttribute(attrNameLower, tagName)) {
105
127
  return;
106
128
  }
107
129
  if (tokenChainObj[attrNameLower]) {
108
- node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
130
+ node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
109
131
  }
110
132
  });
111
133
  return node;
package/dist/helpers.d.ts CHANGED
@@ -7,10 +7,17 @@ declare function isComment(content: PostHTMLNodeLike | null): boolean;
7
7
  declare function isConditionalComment(content: string): boolean;
8
8
  declare function isStyleNode(node: PostHTML.Node): boolean | undefined;
9
9
  declare function extractCssFromStyleNode(node: PostHTML.Node): string | undefined;
10
+ declare function stripCssCdata(css: string): {
11
+ strippedCss: string;
12
+ isCdataWrapped: boolean;
13
+ };
14
+ declare function wrapCssCdata(css: string, isCdataWrapped: boolean): string;
15
+ declare function isCssStyleType(node: PostHTML.Node): boolean;
16
+ declare function normalizeMimeType(value: string): string;
10
17
  declare function isEventHandler(attributeName: string): boolean | "";
11
18
  declare function extractTextContentFromNode(node: PostHTML.Node): string;
12
19
  declare function optionalImport<Module = unknown, Default = Module>(moduleName: string): Promise<(Module & {
13
20
  default?: Default;
14
21
  }) | NonNullable<Default> | null>;
15
22
 
16
- export { extractCssFromStyleNode, extractTextContentFromNode, isAmpBoilerplate, isComment, isConditionalComment, isEventHandler, isStyleNode, optionalImport };
23
+ export { extractCssFromStyleNode, extractTextContentFromNode, isAmpBoilerplate, isComment, isConditionalComment, isCssStyleType, isEventHandler, isStyleNode, normalizeMimeType, optionalImport, stripCssCdata, wrapCssCdata };
package/dist/helpers.js CHANGED
@@ -5,6 +5,8 @@ const ampBoilerplateAttributes = [
5
5
  'amp4ads-boilerplate',
6
6
  'amp4email-boilerplate'
7
7
  ];
8
+ const cssCdataStart = '<![CDATA[';
9
+ const cssCdataEnd = ']]>';
8
10
  function isAmpBoilerplate(node) {
9
11
  if (!node.attrs) {
10
12
  return false;
@@ -32,6 +34,48 @@ function isStyleNode(node) {
32
34
  function extractCssFromStyleNode(node) {
33
35
  return Array.isArray(node.content) ? node.content.join(' ') : node.content;
34
36
  }
37
+ function stripCssCdata(css) {
38
+ const trimmed = css.trim();
39
+ if (!trimmed.startsWith(cssCdataStart) || !trimmed.endsWith(cssCdataEnd)) {
40
+ return {
41
+ strippedCss: css,
42
+ isCdataWrapped: false
43
+ };
44
+ }
45
+ const strippedCss = trimmed.slice(cssCdataStart.length, trimmed.length - cssCdataEnd.length);
46
+ return {
47
+ strippedCss,
48
+ isCdataWrapped: true
49
+ };
50
+ }
51
+ function wrapCssCdata(css, isCdataWrapped) {
52
+ if (!isCdataWrapped) {
53
+ return css;
54
+ }
55
+ return `${cssCdataStart}${css}${cssCdataEnd}`;
56
+ }
57
+ function isCssStyleType(node) {
58
+ if (!node.attrs || !('type' in node.attrs)) {
59
+ return true;
60
+ }
61
+ const rawType = node.attrs.type;
62
+ if (rawType === '') {
63
+ return true;
64
+ }
65
+ if (typeof rawType !== 'string') {
66
+ return false;
67
+ }
68
+ const normalizedType = rawType.trim().toLowerCase();
69
+ return /^text\/css(?:$|\s*;)/.test(normalizedType);
70
+ }
71
+ function normalizeMimeType(value) {
72
+ const trimmed = value.trim();
73
+ if (!trimmed) {
74
+ return '';
75
+ }
76
+ const [mimeType] = trimmed.split(';');
77
+ return mimeType.trim().toLowerCase();
78
+ }
35
79
  function isEventHandler(attributeName) {
36
80
  return attributeName && attributeName.slice(0, 2).toLowerCase() === 'on' && attributeName.length >= 5;
37
81
  }
@@ -67,6 +111,10 @@ exports.extractTextContentFromNode = extractTextContentFromNode;
67
111
  exports.isAmpBoilerplate = isAmpBoilerplate;
68
112
  exports.isComment = isComment;
69
113
  exports.isConditionalComment = isConditionalComment;
114
+ exports.isCssStyleType = isCssStyleType;
70
115
  exports.isEventHandler = isEventHandler;
71
116
  exports.isStyleNode = isStyleNode;
117
+ exports.normalizeMimeType = normalizeMimeType;
72
118
  exports.optionalImport = optionalImport;
119
+ exports.stripCssCdata = stripCssCdata;
120
+ exports.wrapCssCdata = wrapCssCdata;
package/dist/helpers.mjs CHANGED
@@ -3,6 +3,8 @@ const ampBoilerplateAttributes = [
3
3
  'amp4ads-boilerplate',
4
4
  'amp4email-boilerplate'
5
5
  ];
6
+ const cssCdataStart = '<![CDATA[';
7
+ const cssCdataEnd = ']]>';
6
8
  function isAmpBoilerplate(node) {
7
9
  if (!node.attrs) {
8
10
  return false;
@@ -30,6 +32,48 @@ function isStyleNode(node) {
30
32
  function extractCssFromStyleNode(node) {
31
33
  return Array.isArray(node.content) ? node.content.join(' ') : node.content;
32
34
  }
35
+ function stripCssCdata(css) {
36
+ const trimmed = css.trim();
37
+ if (!trimmed.startsWith(cssCdataStart) || !trimmed.endsWith(cssCdataEnd)) {
38
+ return {
39
+ strippedCss: css,
40
+ isCdataWrapped: false
41
+ };
42
+ }
43
+ const strippedCss = trimmed.slice(cssCdataStart.length, trimmed.length - cssCdataEnd.length);
44
+ return {
45
+ strippedCss,
46
+ isCdataWrapped: true
47
+ };
48
+ }
49
+ function wrapCssCdata(css, isCdataWrapped) {
50
+ if (!isCdataWrapped) {
51
+ return css;
52
+ }
53
+ return `${cssCdataStart}${css}${cssCdataEnd}`;
54
+ }
55
+ function isCssStyleType(node) {
56
+ if (!node.attrs || !('type' in node.attrs)) {
57
+ return true;
58
+ }
59
+ const rawType = node.attrs.type;
60
+ if (rawType === '') {
61
+ return true;
62
+ }
63
+ if (typeof rawType !== 'string') {
64
+ return false;
65
+ }
66
+ const normalizedType = rawType.trim().toLowerCase();
67
+ return /^text\/css(?:$|\s*;)/.test(normalizedType);
68
+ }
69
+ function normalizeMimeType(value) {
70
+ const trimmed = value.trim();
71
+ if (!trimmed) {
72
+ return '';
73
+ }
74
+ const [mimeType] = trimmed.split(';');
75
+ return mimeType.trim().toLowerCase();
76
+ }
33
77
  function isEventHandler(attributeName) {
34
78
  return attributeName && attributeName.slice(0, 2).toLowerCase() === 'on' && attributeName.length >= 5;
35
79
  }
@@ -60,4 +104,4 @@ async function optionalImport(moduleName) {
60
104
  }
61
105
  }
62
106
 
63
- export { extractCssFromStyleNode, extractTextContentFromNode, isAmpBoilerplate, isComment, isConditionalComment, isEventHandler, isStyleNode, optionalImport };
107
+ export { extractCssFromStyleNode, extractTextContentFromNode, isAmpBoilerplate, isComment, isConditionalComment, isCssStyleType, isEventHandler, isStyleNode, normalizeMimeType, optionalImport, stripCssCdata, wrapCssCdata };
package/dist/index.d.ts CHANGED
@@ -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
  }
@@ -77,4 +110,4 @@ declare function htmlMinimizerWebpackPluginMinify(input: {
77
110
  }>;
78
111
 
79
112
  export { htmlnano as default, getRequiredOptionalDependencies, htmlMinimizerWebpackPluginMinify, loadConfig, presets, process };
80
- export type { HtmlnanoModule, HtmlnanoModuleAttrsHandler, HtmlnanoModuleContentHandler, HtmlnanoModuleNodeHandler, HtmlnanoOptions, HtmlnanoOptionsConfigFile, HtmlnanoPredefinedPreset, HtmlnanoPredefinedPresets, HtmlnanoPreset, PostHTMLNodeLike, PostHTMLTreeLike };
113
+ export type { HtmlnanoModule, HtmlnanoModuleAttrsHandler, HtmlnanoModuleContentHandler, HtmlnanoModuleNodeHandler, HtmlnanoOptions, HtmlnanoOptionsConfigFile, HtmlnanoPredefinedPreset, HtmlnanoPredefinedPresets, HtmlnanoPreset, HtmlnanoTemplateRule, MinifyHtmlTemplateOptions, PostHTMLNodeLike, PostHTMLTreeLike };
package/dist/index.js CHANGED
@@ -125,14 +125,17 @@ const modules = {
125
125
  mergeStyles: ()=>interop(import('./_modules/mergeStyles.js')),
126
126
  minifyConditionalComments: ()=>interop(import('./_modules/minifyConditionalComments.js')),
127
127
  minifyCss: ()=>interop(import('./_modules/minifyCss.js')),
128
+ minifyHtmlTemplate: ()=>interop(import('./_modules/minifyHtmlTemplate.js')),
128
129
  minifyJs: ()=>interop(import('./_modules/minifyJs.js')),
129
130
  minifyJson: ()=>interop(import('./_modules/minifyJson.js')),
131
+ minifyAttributes: ()=>interop(import('./_modules/minifyAttributes.js')),
130
132
  minifySvg: ()=>interop(import('./_modules/minifySvg.js')),
131
133
  minifyUrls: ()=>interop(import('./_modules/minifyUrls.js')),
132
134
  normalizeAttributeValues: ()=>interop(import('./_modules/normalizeAttributeValues.js')),
133
135
  removeAttributeQuotes: ()=>interop(import('./_modules/removeAttributeQuotes.js')),
134
136
  removeComments: ()=>interop(import('./_modules/removeComments.js')),
135
137
  removeEmptyAttributes: ()=>interop(import('./_modules/removeEmptyAttributes.js')),
138
+ removeEmptyElements: ()=>interop(import('./_modules/removeEmptyElements.js')),
136
139
  removeOptionalTags: ()=>interop(import('./_modules/removeOptionalTags.js')),
137
140
  removeRedundantAttributes: ()=>interop(import('./_modules/removeRedundantAttributes.js')),
138
141
  removeUnusedCss: ()=>interop(import('./_modules/removeUnusedCss.js')),
@@ -152,7 +155,13 @@ const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
152
155
  ...options
153
156
  };
154
157
  let promise = Promise.resolve(tree);
158
+ const nonModuleOptions = new Set([
159
+ 'skipInternalWarnings'
160
+ ]);
155
161
  for (const [moduleName, moduleOptions] of Object.entries(options)){
162
+ if (nonModuleOptions.has(moduleName)) {
163
+ continue;
164
+ }
156
165
  if (!moduleOptions) {
157
166
  continue;
158
167
  }
@@ -179,15 +188,19 @@ const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
179
188
  }
180
189
  const mod = moduleName in modules ? await modules[moduleName]() : await import(`./_modules/${moduleName}.mjs`);
181
190
  if (typeof mod.onAttrs === 'function') {
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
182
192
  attrsHandlers.push(mod.onAttrs(options, moduleOptions));
183
193
  }
184
194
  if (typeof mod.onContent === 'function') {
195
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
185
196
  contentsHandlers.push(mod.onContent(options, moduleOptions));
186
197
  }
187
198
  if (typeof mod.onNode === 'function') {
199
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
188
200
  nodeHandlers.push(mod.onNode(options, moduleOptions));
189
201
  }
190
202
  if (typeof mod.default === 'function') {
203
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
191
204
  promise = promise.then(async (tree)=>await mod.default(tree, options, moduleOptions));
192
205
  }
193
206
  }
package/dist/index.mjs CHANGED
@@ -116,14 +116,17 @@ const modules = {
116
116
  mergeStyles: ()=>interop(import('./_modules/mergeStyles.mjs')),
117
117
  minifyConditionalComments: ()=>interop(import('./_modules/minifyConditionalComments.mjs')),
118
118
  minifyCss: ()=>interop(import('./_modules/minifyCss.mjs')),
119
+ minifyHtmlTemplate: ()=>interop(import('./_modules/minifyHtmlTemplate.mjs')),
119
120
  minifyJs: ()=>interop(import('./_modules/minifyJs.mjs')),
120
121
  minifyJson: ()=>interop(import('./_modules/minifyJson.mjs')),
122
+ minifyAttributes: ()=>interop(import('./_modules/minifyAttributes.mjs')),
121
123
  minifySvg: ()=>interop(import('./_modules/minifySvg.mjs')),
122
124
  minifyUrls: ()=>interop(import('./_modules/minifyUrls.mjs')),
123
125
  normalizeAttributeValues: ()=>interop(import('./_modules/normalizeAttributeValues.mjs')),
124
126
  removeAttributeQuotes: ()=>interop(import('./_modules/removeAttributeQuotes.mjs')),
125
127
  removeComments: ()=>interop(import('./_modules/removeComments.mjs')),
126
128
  removeEmptyAttributes: ()=>interop(import('./_modules/removeEmptyAttributes.mjs')),
129
+ removeEmptyElements: ()=>interop(import('./_modules/removeEmptyElements.mjs')),
127
130
  removeOptionalTags: ()=>interop(import('./_modules/removeOptionalTags.mjs')),
128
131
  removeRedundantAttributes: ()=>interop(import('./_modules/removeRedundantAttributes.mjs')),
129
132
  removeUnusedCss: ()=>interop(import('./_modules/removeUnusedCss.mjs')),
@@ -143,7 +146,13 @@ const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
143
146
  ...options
144
147
  };
145
148
  let promise = Promise.resolve(tree);
149
+ const nonModuleOptions = new Set([
150
+ 'skipInternalWarnings'
151
+ ]);
146
152
  for (const [moduleName, moduleOptions] of Object.entries(options)){
153
+ if (nonModuleOptions.has(moduleName)) {
154
+ continue;
155
+ }
147
156
  if (!moduleOptions) {
148
157
  continue;
149
158
  }
@@ -170,15 +179,19 @@ const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
170
179
  }
171
180
  const mod = moduleName in modules ? await modules[moduleName]() : await import(`./_modules/${moduleName}.mjs`);
172
181
  if (typeof mod.onAttrs === 'function') {
182
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
173
183
  attrsHandlers.push(mod.onAttrs(options, moduleOptions));
174
184
  }
175
185
  if (typeof mod.onContent === 'function') {
186
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
176
187
  contentsHandlers.push(mod.onContent(options, moduleOptions));
177
188
  }
178
189
  if (typeof mod.onNode === 'function') {
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
179
191
  nodeHandlers.push(mod.onNode(options, moduleOptions));
180
192
  }
181
193
  if (typeof mod.default === 'function') {
194
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
182
195
  promise = promise.then(async (tree)=>await mod.default(tree, options, moduleOptions));
183
196
  }
184
197
  }
@@ -2,15 +2,23 @@ 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>;
17
+ type HtmlnanoTemplateRule = {
18
+ tag: string;
19
+ attrs?: Record<string, string | boolean | void>;
20
+ };
21
+ type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
14
22
  interface HtmlnanoOptions {
15
23
  skipConfigLoading?: boolean;
16
24
  configPath?: string;
@@ -26,17 +34,42 @@ interface HtmlnanoOptions {
26
34
  mergeStyles?: boolean;
27
35
  mergeScripts?: boolean;
28
36
  minifyCss?: Options | boolean;
37
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
29
38
  minifyConditionalComments?: boolean;
30
39
  minifyJs?: MinifyOptions | boolean;
31
40
  minifyJson?: boolean;
41
+ minifyAttributes?: boolean | {
42
+ metaContent?: boolean;
43
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
44
+ };
32
45
  minifySvg?: Config | boolean;
33
46
  normalizeAttributeValues?: boolean;
34
- removeAttributeQuotes?: boolean;
35
- removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
47
+ removeAttributeQuotes?: boolean | {
48
+ force?: boolean;
49
+ };
50
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
36
51
  removeEmptyAttributes?: boolean;
52
+ removeEmptyElements?: boolean | {
53
+ removeWithAttributes?: boolean;
54
+ };
37
55
  removeRedundantAttributes?: boolean;
38
56
  removeOptionalTags?: boolean;
39
- removeUnusedCss?: boolean;
57
+ removeUnusedCss?: boolean | ({
58
+ tool: 'purgeCSS';
59
+ } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
60
+ banner?: boolean;
61
+ csspath?: string;
62
+ htmlroot?: string;
63
+ ignore?: (string | RegExp)[];
64
+ inject?: string;
65
+ jsdom?: object;
66
+ media?: string[];
67
+ report?: boolean;
68
+ strictSSL?: boolean;
69
+ timeout?: number;
70
+ uncssrc?: string;
71
+ userAgent?: string;
72
+ };
40
73
  sortAttributes?: boolean | 'alphabetical' | 'frequency';
41
74
  sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
42
75
  }
@@ -2,15 +2,23 @@ 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>;
17
+ type HtmlnanoTemplateRule = {
18
+ tag: string;
19
+ attrs?: Record<string, string | boolean | void>;
20
+ };
21
+ type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
14
22
  interface HtmlnanoOptions {
15
23
  skipConfigLoading?: boolean;
16
24
  configPath?: string;
@@ -26,17 +34,42 @@ interface HtmlnanoOptions {
26
34
  mergeStyles?: boolean;
27
35
  mergeScripts?: boolean;
28
36
  minifyCss?: Options | boolean;
37
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
29
38
  minifyConditionalComments?: boolean;
30
39
  minifyJs?: MinifyOptions | boolean;
31
40
  minifyJson?: boolean;
41
+ minifyAttributes?: boolean | {
42
+ metaContent?: boolean;
43
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
44
+ };
32
45
  minifySvg?: Config | boolean;
33
46
  normalizeAttributeValues?: boolean;
34
- removeAttributeQuotes?: boolean;
35
- removeComments?: boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
47
+ removeAttributeQuotes?: boolean | {
48
+ force?: boolean;
49
+ };
50
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
36
51
  removeEmptyAttributes?: boolean;
52
+ removeEmptyElements?: boolean | {
53
+ removeWithAttributes?: boolean;
54
+ };
37
55
  removeRedundantAttributes?: boolean;
38
56
  removeOptionalTags?: boolean;
39
- removeUnusedCss?: boolean;
57
+ removeUnusedCss?: boolean | ({
58
+ tool: 'purgeCSS';
59
+ } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
60
+ banner?: boolean;
61
+ csspath?: string;
62
+ htmlroot?: string;
63
+ ignore?: (string | RegExp)[];
64
+ inject?: string;
65
+ jsdom?: object;
66
+ media?: string[];
67
+ report?: boolean;
68
+ strictSSL?: boolean;
69
+ timeout?: number;
70
+ uncssrc?: string;
71
+ userAgent?: string;
72
+ };
40
73
  sortAttributes?: boolean | 'alphabetical' | 'frequency';
41
74
  sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
42
75
  }