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
@@ -26,20 +26,25 @@ var removeRedundantAttributes_js = require('./removeRedundantAttributes.js');
26
26
  return node;
27
27
  }
28
28
  if (node.tag && node.tag === 'script') {
29
- const mimeType = nodeAttrs.type || 'text/javascript';
29
+ const rawMimeType = typeof nodeAttrs.type === 'string' ? helpers_js.normalizeMimeType(nodeAttrs.type) : undefined;
30
+ const mimeType = rawMimeType || 'text/javascript';
30
31
  if (removeRedundantAttributes_js.redundantScriptTypes.has(mimeType) || mimeType === 'module') {
31
- p = processScriptNode(node, terserOptions, terser);
32
+ const scriptTerserOptions = resolveScriptTerserOptions(terserOptions, mimeType);
33
+ p = processScriptNode(node, scriptTerserOptions, terser);
32
34
  if (p) {
33
35
  promises.push(p);
34
36
  }
35
37
  }
36
38
  }
37
39
  if (node.attrs) {
38
- promises.push.apply(processNodeWithOnAttrs(node, terserOptions, terser));
40
+ promises.push(...processNodeWithOnAttrs(node, terserOptions, terser));
39
41
  }
40
42
  return node;
41
43
  });
42
- return Promise.all(promises).then(()=>tree);
44
+ return Promise.all(promises).then(()=>{
45
+ applySmartQuoteOptions(tree);
46
+ return tree;
47
+ });
43
48
  }
44
49
  };
45
50
  function stripCdata(js) {
@@ -50,6 +55,89 @@ function stripCdata(js) {
50
55
  const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
51
56
  return leftStrippedJs === strippedJs ? js : strippedJs;
52
57
  }
58
+ function resolveScriptTerserOptions(terserOptions, mimeType) {
59
+ var _terserOptions_toplevel, _terserOptions_compress, _terserOptions_mangle;
60
+ if (mimeType !== 'module' || terserOptions.module !== undefined) {
61
+ return terserOptions;
62
+ }
63
+ return {
64
+ ...terserOptions,
65
+ module: true,
66
+ toplevel: (_terserOptions_toplevel = terserOptions.toplevel) != null ? _terserOptions_toplevel : false,
67
+ compress: (_terserOptions_compress = terserOptions.compress) != null ? _terserOptions_compress : false,
68
+ mangle: (_terserOptions_mangle = terserOptions.mangle) != null ? _terserOptions_mangle : false
69
+ };
70
+ }
71
+ function resolveOnAttrTerserOptions(terserOptions) {
72
+ const output = terserOptions.output;
73
+ const format = terserOptions.format;
74
+ const outputHasQuoteStyle = !!(output && typeof output === 'object' && 'quote_style' in output);
75
+ const formatHasQuoteStyle = !!(format && typeof format === 'object' && 'quote_style' in format);
76
+ if (outputHasQuoteStyle || formatHasQuoteStyle) {
77
+ return terserOptions;
78
+ }
79
+ const resolved = {
80
+ ...terserOptions
81
+ };
82
+ if (format && typeof format === 'object') {
83
+ resolved.format = {
84
+ ...format,
85
+ ['quote_style']: 3
86
+ };
87
+ }
88
+ if (output && typeof output === 'object') {
89
+ resolved.output = {
90
+ ...output,
91
+ ['quote_style']: 3
92
+ };
93
+ }
94
+ if (!format && !output) {
95
+ resolved.output = {
96
+ ['quote_style']: 3
97
+ };
98
+ }
99
+ return resolved;
100
+ }
101
+ function applySmartQuoteOptions(tree) {
102
+ var _tree, _options, _tree_options, _quoteStyle, _tree_options1, _replaceQuote;
103
+ const quoteState = analyzeTreeQuotes(tree);
104
+ if (!quoteState.needsSmartQuotes || quoteState.hasMixedQuotes) {
105
+ return;
106
+ }
107
+ (_options = (_tree = tree).options) != null ? _options : _tree.options = {};
108
+ (_quoteStyle = (_tree_options = tree.options).quoteStyle) != null ? _quoteStyle : _tree_options.quoteStyle = 0;
109
+ (_replaceQuote = (_tree_options1 = tree.options).replaceQuote) != null ? _replaceQuote : _tree_options1.replaceQuote = false;
110
+ }
111
+ function analyzeTreeQuotes(tree) {
112
+ let needsSmartQuotes = false;
113
+ let hasMixedQuotes = false;
114
+ tree.walk((node)=>{
115
+ if (!node || !node.attrs) {
116
+ return node;
117
+ }
118
+ for (const [attrName, attrValue] of Object.entries(node.attrs)){
119
+ if (typeof attrValue !== 'string') {
120
+ continue;
121
+ }
122
+ const hasDoubleQuote = attrValue.includes('"');
123
+ const hasSingleQuote = attrValue.includes('\'');
124
+ if (hasDoubleQuote && helpers_js.isEventHandler(attrName)) {
125
+ needsSmartQuotes = true;
126
+ }
127
+ if (hasDoubleQuote && hasSingleQuote) {
128
+ hasMixedQuotes = true;
129
+ }
130
+ if (needsSmartQuotes && hasMixedQuotes) {
131
+ return node;
132
+ }
133
+ }
134
+ return node;
135
+ });
136
+ return {
137
+ needsSmartQuotes,
138
+ hasMixedQuotes
139
+ };
140
+ }
53
141
  function processScriptNode(scriptNode, terserOptions, terser) {
54
142
  let js = helpers_js.extractTextContentFromNode(scriptNode).trim();
55
143
  if (!js.length) {
@@ -81,6 +169,7 @@ function processScriptNode(scriptNode, terserOptions, terser) {
81
169
  function processNodeWithOnAttrs(node, terserOptions, terser) {
82
170
  const jsWrapperStart = 'a=function(){';
83
171
  const jsWrapperEnd = '};a();';
172
+ const onAttrTerserOptions = resolveOnAttrTerserOptions(terserOptions);
84
173
  const promises = [];
85
174
  if (!node.attrs) {
86
175
  return promises;
@@ -98,15 +187,27 @@ function processNodeWithOnAttrs(node, terserOptions, terser) {
98
187
  // Therefore the attribute's code should be wrapped inside function:
99
188
  // "function _(){return false;}"
100
189
  const wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
101
- const promise = terser.minify(wrappedJs, terserOptions).then(({ code })=>{
190
+ const promise = terser.minify(wrappedJs, onAttrTerserOptions).then(({ code })=>{
102
191
  if (code) {
103
192
  const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
104
193
  node.attrs[attrName] = minifiedJs;
105
194
  }
195
+ }).catch((error)=>{
196
+ // Skip invalid inline handler code and preserve the original value.
197
+ if (isTerserParseError(error)) {
198
+ return;
199
+ }
200
+ throw error;
106
201
  });
107
202
  promises.push(promise);
108
203
  }
109
204
  return promises;
110
205
  }
206
+ function isTerserParseError(error) {
207
+ if (!(error instanceof Error)) {
208
+ return false;
209
+ }
210
+ return error.name === 'SyntaxError' || error.message.includes('JS_Parse_Error');
211
+ }
111
212
 
112
213
  exports.default = mod;
@@ -1,4 +1,4 @@
1
- import { optionalImport, extractTextContentFromNode, isEventHandler } from '../helpers.mjs';
1
+ import { optionalImport, normalizeMimeType, isEventHandler, extractTextContentFromNode } from '../helpers.mjs';
2
2
  import { redundantScriptTypes } from './removeRedundantAttributes.mjs';
3
3
 
4
4
  /** Minify JS with Terser */ const mod = {
@@ -24,20 +24,25 @@ import { redundantScriptTypes } from './removeRedundantAttributes.mjs';
24
24
  return node;
25
25
  }
26
26
  if (node.tag && node.tag === 'script') {
27
- const mimeType = nodeAttrs.type || 'text/javascript';
27
+ const rawMimeType = typeof nodeAttrs.type === 'string' ? normalizeMimeType(nodeAttrs.type) : undefined;
28
+ const mimeType = rawMimeType || 'text/javascript';
28
29
  if (redundantScriptTypes.has(mimeType) || mimeType === 'module') {
29
- p = processScriptNode(node, terserOptions, terser);
30
+ const scriptTerserOptions = resolveScriptTerserOptions(terserOptions, mimeType);
31
+ p = processScriptNode(node, scriptTerserOptions, terser);
30
32
  if (p) {
31
33
  promises.push(p);
32
34
  }
33
35
  }
34
36
  }
35
37
  if (node.attrs) {
36
- promises.push.apply(processNodeWithOnAttrs(node, terserOptions, terser));
38
+ promises.push(...processNodeWithOnAttrs(node, terserOptions, terser));
37
39
  }
38
40
  return node;
39
41
  });
40
- return Promise.all(promises).then(()=>tree);
42
+ return Promise.all(promises).then(()=>{
43
+ applySmartQuoteOptions(tree);
44
+ return tree;
45
+ });
41
46
  }
42
47
  };
43
48
  function stripCdata(js) {
@@ -48,6 +53,89 @@ function stripCdata(js) {
48
53
  const strippedJs = leftStrippedJs.replace(/\/\/\s*\]\]>/, '').replace(/\/\*\s*\]\]>\s*\*\//, '');
49
54
  return leftStrippedJs === strippedJs ? js : strippedJs;
50
55
  }
56
+ function resolveScriptTerserOptions(terserOptions, mimeType) {
57
+ var _terserOptions_toplevel, _terserOptions_compress, _terserOptions_mangle;
58
+ if (mimeType !== 'module' || terserOptions.module !== undefined) {
59
+ return terserOptions;
60
+ }
61
+ return {
62
+ ...terserOptions,
63
+ module: true,
64
+ toplevel: (_terserOptions_toplevel = terserOptions.toplevel) != null ? _terserOptions_toplevel : false,
65
+ compress: (_terserOptions_compress = terserOptions.compress) != null ? _terserOptions_compress : false,
66
+ mangle: (_terserOptions_mangle = terserOptions.mangle) != null ? _terserOptions_mangle : false
67
+ };
68
+ }
69
+ function resolveOnAttrTerserOptions(terserOptions) {
70
+ const output = terserOptions.output;
71
+ const format = terserOptions.format;
72
+ const outputHasQuoteStyle = !!(output && typeof output === 'object' && 'quote_style' in output);
73
+ const formatHasQuoteStyle = !!(format && typeof format === 'object' && 'quote_style' in format);
74
+ if (outputHasQuoteStyle || formatHasQuoteStyle) {
75
+ return terserOptions;
76
+ }
77
+ const resolved = {
78
+ ...terserOptions
79
+ };
80
+ if (format && typeof format === 'object') {
81
+ resolved.format = {
82
+ ...format,
83
+ ['quote_style']: 3
84
+ };
85
+ }
86
+ if (output && typeof output === 'object') {
87
+ resolved.output = {
88
+ ...output,
89
+ ['quote_style']: 3
90
+ };
91
+ }
92
+ if (!format && !output) {
93
+ resolved.output = {
94
+ ['quote_style']: 3
95
+ };
96
+ }
97
+ return resolved;
98
+ }
99
+ function applySmartQuoteOptions(tree) {
100
+ var _tree, _options, _tree_options, _quoteStyle, _tree_options1, _replaceQuote;
101
+ const quoteState = analyzeTreeQuotes(tree);
102
+ if (!quoteState.needsSmartQuotes || quoteState.hasMixedQuotes) {
103
+ return;
104
+ }
105
+ (_options = (_tree = tree).options) != null ? _options : _tree.options = {};
106
+ (_quoteStyle = (_tree_options = tree.options).quoteStyle) != null ? _quoteStyle : _tree_options.quoteStyle = 0;
107
+ (_replaceQuote = (_tree_options1 = tree.options).replaceQuote) != null ? _replaceQuote : _tree_options1.replaceQuote = false;
108
+ }
109
+ function analyzeTreeQuotes(tree) {
110
+ let needsSmartQuotes = false;
111
+ let hasMixedQuotes = false;
112
+ tree.walk((node)=>{
113
+ if (!node || !node.attrs) {
114
+ return node;
115
+ }
116
+ for (const [attrName, attrValue] of Object.entries(node.attrs)){
117
+ if (typeof attrValue !== 'string') {
118
+ continue;
119
+ }
120
+ const hasDoubleQuote = attrValue.includes('"');
121
+ const hasSingleQuote = attrValue.includes('\'');
122
+ if (hasDoubleQuote && isEventHandler(attrName)) {
123
+ needsSmartQuotes = true;
124
+ }
125
+ if (hasDoubleQuote && hasSingleQuote) {
126
+ hasMixedQuotes = true;
127
+ }
128
+ if (needsSmartQuotes && hasMixedQuotes) {
129
+ return node;
130
+ }
131
+ }
132
+ return node;
133
+ });
134
+ return {
135
+ needsSmartQuotes,
136
+ hasMixedQuotes
137
+ };
138
+ }
51
139
  function processScriptNode(scriptNode, terserOptions, terser) {
52
140
  let js = extractTextContentFromNode(scriptNode).trim();
53
141
  if (!js.length) {
@@ -79,6 +167,7 @@ function processScriptNode(scriptNode, terserOptions, terser) {
79
167
  function processNodeWithOnAttrs(node, terserOptions, terser) {
80
168
  const jsWrapperStart = 'a=function(){';
81
169
  const jsWrapperEnd = '};a();';
170
+ const onAttrTerserOptions = resolveOnAttrTerserOptions(terserOptions);
82
171
  const promises = [];
83
172
  if (!node.attrs) {
84
173
  return promises;
@@ -96,15 +185,27 @@ function processNodeWithOnAttrs(node, terserOptions, terser) {
96
185
  // Therefore the attribute's code should be wrapped inside function:
97
186
  // "function _(){return false;}"
98
187
  const wrappedJs = jsWrapperStart + node.attrs[attrName] + jsWrapperEnd;
99
- const promise = terser.minify(wrappedJs, terserOptions).then(({ code })=>{
188
+ const promise = terser.minify(wrappedJs, onAttrTerserOptions).then(({ code })=>{
100
189
  if (code) {
101
190
  const minifiedJs = code.substring(jsWrapperStart.length, code.length - jsWrapperEnd.length);
102
191
  node.attrs[attrName] = minifiedJs;
103
192
  }
193
+ }).catch((error)=>{
194
+ // Skip invalid inline handler code and preserve the original value.
195
+ if (isTerserParseError(error)) {
196
+ return;
197
+ }
198
+ throw error;
104
199
  });
105
200
  promises.push(promise);
106
201
  }
107
202
  return promises;
108
203
  }
204
+ function isTerserParseError(error) {
205
+ if (!(error instanceof Error)) {
206
+ return false;
207
+ }
208
+ return error.name === 'SyntaxError' || error.message.includes('JS_Parse_Error');
209
+ }
109
210
 
110
211
  export { mod as default };
@@ -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
  }
@@ -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
  }
@@ -1,6 +1,8 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
- const rNodeAttrsTypeJson = /(\/|\+)json/;
3
+ var helpers_js = require('../helpers.js');
4
+
5
+ const rNodeAttrsTypeJson = /(?:\/|\+)json$/i;
4
6
  const mod = {
5
7
  onContent () {
6
8
  return (content, node)=>{
@@ -8,17 +10,12 @@ const mod = {
8
10
  if (node.attrs && 'integrity' in node.attrs) {
9
11
  return content;
10
12
  }
11
- if (node.attrs && node.attrs.type && rNodeAttrsTypeJson.test(node.attrs.type)) {
13
+ const nodeType = node.attrs && typeof node.attrs.type === 'string' ? helpers_js.normalizeMimeType(node.attrs.type) : undefined;
14
+ if (nodeType && rNodeAttrsTypeJson.test(nodeType)) {
12
15
  try {
13
- // cast minified JSON to an array
14
- let jsonContent = '';
15
- for(let i = 0, len = content.length; i < len; i++){
16
- const item = content[i];
17
- if (typeof item === 'string') {
18
- jsonContent += item;
19
- } else {
20
- return content; // If any item is not a string, return original contents
21
- }
16
+ const jsonContent = typeof content === 'string' ? content : Array.isArray(content) && content.every((item)=>typeof item === 'string') ? content.join('') : null;
17
+ if (jsonContent === null) {
18
+ return content;
22
19
  }
23
20
  return [
24
21
  JSON.stringify(JSON.parse(jsonContent))
@@ -1,4 +1,6 @@
1
- const rNodeAttrsTypeJson = /(\/|\+)json/;
1
+ import { normalizeMimeType } from '../helpers.mjs';
2
+
3
+ const rNodeAttrsTypeJson = /(?:\/|\+)json$/i;
2
4
  const mod = {
3
5
  onContent () {
4
6
  return (content, node)=>{
@@ -6,17 +8,12 @@ const mod = {
6
8
  if (node.attrs && 'integrity' in node.attrs) {
7
9
  return content;
8
10
  }
9
- if (node.attrs && node.attrs.type && rNodeAttrsTypeJson.test(node.attrs.type)) {
11
+ const nodeType = node.attrs && typeof node.attrs.type === 'string' ? normalizeMimeType(node.attrs.type) : undefined;
12
+ if (nodeType && rNodeAttrsTypeJson.test(nodeType)) {
10
13
  try {
11
- // cast minified JSON to an array
12
- let jsonContent = '';
13
- for(let i = 0, len = content.length; i < len; i++){
14
- const item = content[i];
15
- if (typeof item === 'string') {
16
- jsonContent += item;
17
- } else {
18
- return content; // If any item is not a string, return original contents
19
- }
14
+ const jsonContent = typeof content === 'string' ? content : Array.isArray(content) && content.every((item)=>typeof item === 'string') ? content.join('') : null;
15
+ if (jsonContent === null) {
16
+ return content;
20
17
  }
21
18
  return [
22
19
  JSON.stringify(JSON.parse(jsonContent))
@@ -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
  }
@@ -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
  }