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,52 +1,68 @@
1
- import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
1
+ import { isListAttribute } from './collapseAttributeWhitespace.mjs';
2
2
 
3
- // class, rel, ping
4
3
  const validOptions = new Set([
5
4
  'frequency',
6
5
  'alphabetical'
7
6
  ]);
8
- const processModuleOptions = (options)=>{
7
+ function resolveSortType(options) {
9
8
  if (options === true) return 'alphabetical';
10
9
  if (options === false) return false;
11
10
  return validOptions.has(options) ? options : false;
12
- };
13
- class AttributeTokenChain {
11
+ }
12
+
13
+ // class, rel, ping
14
+ class ListAttributeTokenChain {
14
15
  addFromNodeAttrsArray(attrValuesArray) {
15
16
  attrValuesArray.forEach((attrValue)=>{
16
- if (this.freqData.has(attrValue)) {
17
- this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
17
+ if (!attrValue) {
18
+ return;
19
+ }
20
+ if (this.tokenCounts.has(attrValue)) {
21
+ this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
18
22
  } else {
19
- this.freqData.set(attrValue, 1);
23
+ this.tokenCounts.set(attrValue, 1);
20
24
  }
21
25
  });
22
26
  }
23
27
  createSortOrder() {
24
- const _sortOrder = [
25
- ...this.freqData.entries()
28
+ const nextSortOrder = [
29
+ ...this.tokenCounts.entries()
26
30
  ];
27
- _sortOrder.sort((a, b)=>b[1] - a[1]);
28
- this.sortOrder = _sortOrder.map((i)=>i[0]);
31
+ nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
32
+ this.sortedTokens = nextSortOrder.map((i)=>i[0]);
29
33
  }
30
34
  sortFromNodeAttrsArray(attrValuesArray) {
31
35
  const resultArray = [];
32
- if (!this.sortOrder) {
36
+ const tokenCounts = new Map();
37
+ attrValuesArray.forEach((attrValue)=>{
38
+ var _tokenCounts_get;
39
+ if (!attrValue) {
40
+ return;
41
+ }
42
+ tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
43
+ });
44
+ if (!this.sortedTokens) {
33
45
  this.createSortOrder();
34
46
  }
35
- this.sortOrder.forEach((k)=>{
36
- if (attrValuesArray.includes(k)) {
47
+ this.sortedTokens.forEach((k)=>{
48
+ const count = tokenCounts.get(k);
49
+ if (!count) {
50
+ return;
51
+ }
52
+ for(let i = 0; i < count; i += 1){
37
53
  resultArray.push(k);
38
54
  }
39
55
  });
40
56
  return resultArray;
41
57
  }
42
58
  constructor(){
43
- /** <attr, frequency> */ this.freqData = new Map();
44
- this.sortOrder = null;
59
+ /** <attr, frequency> */ this.tokenCounts = new Map();
60
+ this.sortedTokens = null;
45
61
  }
46
62
  }
47
63
  /** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
48
64
  default (tree, options, moduleOptions) {
49
- const sortType = processModuleOptions(moduleOptions);
65
+ const sortType = resolveSortType(moduleOptions);
50
66
  if (sortType === 'alphabetical') {
51
67
  return sortAttributesWithListsInAlphabeticalOrder(tree);
52
68
  }
@@ -57,58 +73,51 @@ class AttributeTokenChain {
57
73
  return tree;
58
74
  }
59
75
  };
60
- function sortAttributesWithListsInAlphabeticalOrder(tree) {
76
+ const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
77
+ function walkListAttributes(tree, walkFn) {
61
78
  tree.walk((node)=>{
62
79
  if (!node.attrs) {
63
80
  return node;
64
81
  }
65
- Object.keys(node.attrs).forEach((attrName)=>{
82
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
83
+ Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
66
84
  const attrNameLower = attrName.toLowerCase();
67
- if (!attributesWithLists.has(attrNameLower)) {
85
+ if (!isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
68
86
  return;
69
87
  }
70
- const attrValues = node.attrs[attrName].split(/\s/);
71
- node.attrs[attrName] = attrValues.sort((a, b)=>{
72
- // @ts-expect-error -- deliberately use minus operator to sort things
73
- return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
74
- }).join(' ');
88
+ walkFn(node.attrs, attrName, attrValues);
75
89
  });
76
90
  return node;
77
91
  });
92
+ }
93
+ function sortAttributesWithListsInAlphabeticalOrder(tree) {
94
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
95
+ const values = splitListAttributeValues(attrValues);
96
+ if (values.length < 2) {
97
+ return;
98
+ }
99
+ nodeAttrs[attrName] = values.sort((a, b)=>{
100
+ // @ts-expect-error -- deliberately use minus operator to sort things
101
+ return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
102
+ }).join(' ');
103
+ });
78
104
  return tree;
79
105
  }
80
106
  function sortAttributesWithListsByFrequency(tree) {
81
- const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
107
+ const tokenChainObj = {};
82
108
  // Traverse through tree to get frequency
83
- tree.walk((node)=>{
84
- if (!node.attrs) {
85
- return node;
86
- }
87
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
88
- const attrNameLower = attrName.toLowerCase();
89
- if (!attributesWithLists.has(attrNameLower)) {
90
- return;
91
- }
92
- tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
93
- tokenChainObj[attrNameLower].addFromNodeAttrsArray(attrValues.split(/\s/));
94
- });
95
- return node;
109
+ walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
110
+ const attrNameLower = attrName.toLowerCase();
111
+ tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
112
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
96
113
  });
97
114
  // Traverse through tree again, this time sort the attribute values
98
- tree.walk((node)=>{
99
- if (!node.attrs) {
100
- return node;
115
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
116
+ const attrNameLower = attrName.toLowerCase();
117
+ if (!tokenChainObj[attrNameLower]) {
118
+ return;
101
119
  }
102
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
103
- const attrNameLower = attrName.toLowerCase();
104
- if (!attributesWithLists.has(attrNameLower)) {
105
- return;
106
- }
107
- if (tokenChainObj[attrNameLower]) {
108
- node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(attrValues.split(/\s/)).join(' ');
109
- }
110
- });
111
- return node;
120
+ nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
112
121
  });
113
122
  return tree;
114
123
  }
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
  }