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
@@ -213,15 +213,17 @@ const mod = {
213
213
  const newAttrs = attrs;
214
214
  Object.entries(attrs).forEach(([attrName, attrValue])=>{
215
215
  let newAttrValue = attrValue;
216
- if (Object.hasOwnProperty.call(caseInsensitiveAttributes, attrName) && (caseInsensitiveAttributes[attrName] === null || node.tag && caseInsensitiveAttributes[attrName].includes(node.tag))) {
217
- newAttrValue = typeof attrValue === 'string' ? attrValue.toLowerCase() : attrValue;
216
+ const hasCaseInsensitive = Object.hasOwnProperty.call(caseInsensitiveAttributes, attrName) && (caseInsensitiveAttributes[attrName] === null || node.tag && caseInsensitiveAttributes[attrName].includes(node.tag));
217
+ const hasInvalidValueDefault = Object.hasOwnProperty.call(invalidValueDefault, attrName);
218
+ const invalidMeta = hasInvalidValueDefault ? invalidValueDefault[attrName] : undefined;
219
+ const appliesInvalidValueDefault = Boolean(invalidMeta && (invalidMeta.tag === null || node && node.tag && invalidMeta.tag.includes(node.tag)));
220
+ const shouldNormalizeCase = hasCaseInsensitive || appliesInvalidValueDefault;
221
+ if (typeof attrValue === 'string' && shouldNormalizeCase) {
222
+ newAttrValue = attrValue.trim().toLowerCase();
218
223
  }
219
- if (Object.hasOwnProperty.call(invalidValueDefault, attrName)) {
220
- const meta = invalidValueDefault[attrName];
221
- if (meta.tag === null || node && node.tag && meta.tag.includes(node.tag)) {
222
- if (typeof newAttrValue !== 'string' || !meta.valid.includes(newAttrValue)) {
223
- newAttrValue = meta.default;
224
- }
224
+ if (appliesInvalidValueDefault && invalidMeta) {
225
+ if (typeof newAttrValue !== 'string' || !invalidMeta.valid.includes(newAttrValue)) {
226
+ newAttrValue = invalidMeta.default;
225
227
  }
226
228
  }
227
229
  newAttrs[attrName] = newAttrValue;
@@ -211,15 +211,17 @@ const mod = {
211
211
  const newAttrs = attrs;
212
212
  Object.entries(attrs).forEach(([attrName, attrValue])=>{
213
213
  let newAttrValue = attrValue;
214
- if (Object.hasOwnProperty.call(caseInsensitiveAttributes, attrName) && (caseInsensitiveAttributes[attrName] === null || node.tag && caseInsensitiveAttributes[attrName].includes(node.tag))) {
215
- newAttrValue = typeof attrValue === 'string' ? attrValue.toLowerCase() : attrValue;
214
+ const hasCaseInsensitive = Object.hasOwnProperty.call(caseInsensitiveAttributes, attrName) && (caseInsensitiveAttributes[attrName] === null || node.tag && caseInsensitiveAttributes[attrName].includes(node.tag));
215
+ const hasInvalidValueDefault = Object.hasOwnProperty.call(invalidValueDefault, attrName);
216
+ const invalidMeta = hasInvalidValueDefault ? invalidValueDefault[attrName] : undefined;
217
+ const appliesInvalidValueDefault = Boolean(invalidMeta && (invalidMeta.tag === null || node && node.tag && invalidMeta.tag.includes(node.tag)));
218
+ const shouldNormalizeCase = hasCaseInsensitive || appliesInvalidValueDefault;
219
+ if (typeof attrValue === 'string' && shouldNormalizeCase) {
220
+ newAttrValue = attrValue.trim().toLowerCase();
216
221
  }
217
- if (Object.hasOwnProperty.call(invalidValueDefault, attrName)) {
218
- const meta = invalidValueDefault[attrName];
219
- if (meta.tag === null || node && node.tag && meta.tag.includes(node.tag)) {
220
- if (typeof newAttrValue !== 'string' || !meta.valid.includes(newAttrValue)) {
221
- newAttrValue = meta.default;
222
- }
222
+ if (appliesInvalidValueDefault && invalidMeta) {
223
+ if (typeof newAttrValue !== 'string' || !invalidMeta.valid.includes(newAttrValue)) {
224
+ newAttrValue = invalidMeta.default;
223
225
  }
224
226
  }
225
227
  newAttrs[attrName] = newAttrValue;
@@ -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
  }
@@ -52,6 +85,9 @@ type HtmlnanoModule<Options = any> = {
52
85
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
53
86
  };
54
87
 
55
- declare const mod: HtmlnanoModule;
88
+ type RemoveAttributeQuotesOptions = {
89
+ force?: boolean;
90
+ };
91
+ declare const mod: HtmlnanoModule<RemoveAttributeQuotesOptions>;
56
92
 
57
93
  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
  }
@@ -52,6 +85,9 @@ type HtmlnanoModule<Options = any> = {
52
85
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
53
86
  };
54
87
 
55
- declare const mod: HtmlnanoModule;
88
+ type RemoveAttributeQuotesOptions = {
89
+ force?: boolean;
90
+ };
91
+ declare const mod: HtmlnanoModule<RemoveAttributeQuotesOptions>;
56
92
 
57
93
  export { mod as default };
@@ -2,11 +2,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  // Specification: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
4
4
  // See also: https://github.com/posthtml/posthtml-render/pull/30
5
- // See also: https://github.com/posthtml/htmlnano/issues/6#issuecomment-707105334
5
+ // See also: https://github.com/maltsev/htmlnano/issues/6#issuecomment-707105334
6
6
  /** Disable quoteAllAttributes while not overriding the configuration */ const mod = {
7
- default: function removeAttributeQuotes(tree) {
8
- var _tree_options, _quoteAllAttributes;
9
- if (tree.options) (_quoteAllAttributes = (_tree_options = tree.options).quoteAllAttributes) != null ? _quoteAllAttributes : _tree_options.quoteAllAttributes = false;
7
+ default: function removeAttributeQuotes(tree, _options, moduleOptions) {
8
+ var _tree, _options1, _tree_options, _quoteAllAttributes;
9
+ (_options1 = (_tree = tree).options) != null ? _options1 : _tree.options = {};
10
+ if (moduleOptions && typeof moduleOptions === 'object' && moduleOptions.force) {
11
+ tree.options.quoteAllAttributes = false;
12
+ return tree;
13
+ }
14
+ (_quoteAllAttributes = (_tree_options = tree.options).quoteAllAttributes) != null ? _quoteAllAttributes : _tree_options.quoteAllAttributes = false;
10
15
  return tree;
11
16
  }
12
17
  };
@@ -1,10 +1,15 @@
1
1
  // Specification: https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
2
2
  // See also: https://github.com/posthtml/posthtml-render/pull/30
3
- // See also: https://github.com/posthtml/htmlnano/issues/6#issuecomment-707105334
3
+ // See also: https://github.com/maltsev/htmlnano/issues/6#issuecomment-707105334
4
4
  /** Disable quoteAllAttributes while not overriding the configuration */ const mod = {
5
- default: function removeAttributeQuotes(tree) {
6
- var _tree_options, _quoteAllAttributes;
7
- if (tree.options) (_quoteAllAttributes = (_tree_options = tree.options).quoteAllAttributes) != null ? _quoteAllAttributes : _tree_options.quoteAllAttributes = false;
5
+ default: function removeAttributeQuotes(tree, _options, moduleOptions) {
6
+ var _tree, _options1, _tree_options, _quoteAllAttributes;
7
+ (_options1 = (_tree = tree).options) != null ? _options1 : _tree.options = {};
8
+ if (moduleOptions && typeof moduleOptions === 'object' && moduleOptions.force) {
9
+ tree.options.quoteAllAttributes = false;
10
+ return tree;
11
+ }
12
+ (_quoteAllAttributes = (_tree_options = tree.options).quoteAllAttributes) != null ? _quoteAllAttributes : _tree_options.quoteAllAttributes = false;
8
13
  return tree;
9
14
  }
10
15
  };
@@ -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
  }
@@ -52,7 +85,7 @@ type HtmlnanoModule<Options = any> = {
52
85
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
53
86
  };
54
87
 
55
- type RemoveCommentsOptions = boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
88
+ type RemoveCommentsOptions = boolean | RegExp | ((comment: string) => boolean) | string;
56
89
  declare const mod: HtmlnanoModule<RemoveCommentsOptions>;
57
90
 
58
91
  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
  }
@@ -52,7 +85,7 @@ type HtmlnanoModule<Options = any> = {
52
85
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
53
86
  };
54
87
 
55
- type RemoveCommentsOptions = boolean | 'safe' | 'all' | RegExp | ((comment: string) => boolean);
88
+ type RemoveCommentsOptions = boolean | RegExp | ((comment: string) => boolean) | string;
56
89
  declare const mod: HtmlnanoModule<RemoveCommentsOptions>;
57
90
 
58
91
  export { mod as default };
@@ -2,12 +2,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var helpers_js = require('../helpers.js');
4
4
 
5
- const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
5
+ const MATCH_EXCERPT_REGEXP = /^\s*more\b/i;
6
+ const MATCH_NOINDEX_REGEXP = /^\s*\/?\s*noindex\s*$/i;
7
+ const MATCH_SSE_REGEXP = /^\s*\/?\s*sse\s*$/i;
6
8
  /** Removes HTML comments */ const mod = {
7
9
  onNode (_, removeType) {
8
- if (removeType !== 'all' && removeType !== 'safe' && !isMatcher(removeType)) {
9
- removeType = 'safe';
10
- }
10
+ removeType = normalizeRemoveType(removeType);
11
11
  return (node)=>{
12
12
  if (isCommentToRemove(node, removeType)) {
13
13
  return '';
@@ -16,9 +16,7 @@ const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
16
16
  };
17
17
  },
18
18
  onContent (_, removeType) {
19
- if (removeType !== 'all' && removeType !== 'safe' && !isMatcher(removeType)) {
20
- removeType = 'safe';
21
- }
19
+ removeType = normalizeRemoveType(removeType);
22
20
  return (contents)=>{
23
21
  return contents.filter((content)=>!isCommentToRemove(content, removeType));
24
22
  };
@@ -33,13 +31,13 @@ function isCommentToRemove(text, removeType) {
33
31
  return false;
34
32
  }
35
33
  if (removeType === 'safe') {
36
- const isNoindex = text === '<!--noindex-->' || text === '<!--/noindex-->';
37
- // Don't remove noindex comments.
38
- // See: https://yandex.com/support/webmaster/controlling-robot/html.xml
34
+ const commentBody = getCommentBody(text);
35
+ const isNoindex = commentBody ? MATCH_NOINDEX_REGEXP.test(commentBody) : false;
36
+ // Don't remove noindex comments. It was used by some search engines in the past.
39
37
  if (isNoindex) {
40
38
  return false;
41
39
  }
42
- const isServerSideExclude = text === '<!--sse-->' || text === '<!--/sse-->';
40
+ const isServerSideExclude = commentBody ? MATCH_SSE_REGEXP.test(commentBody) : false;
43
41
  // Don't remove sse comments.
44
42
  // See: https://support.cloudflare.com/hc/en-us/articles/200170036-What-does-Server-Side-Excludes-SSE-do-
45
43
  if (isServerSideExclude) {
@@ -53,7 +51,7 @@ function isCommentToRemove(text, removeType) {
53
51
  // Hugo: https://gohugo.io/content-management/summaries/#manual-summary-splitting
54
52
  // WordPress: https://wordpress.com/support/wordpress-editor/blocks/more-block/2/
55
53
  // Jekyll: https://jekyllrb.com/docs/posts/#post-excerpts
56
- const isCMSExcerptComment = MATCH_EXCERPT_REGEXP.test(text);
54
+ const isCMSExcerptComment = commentBody ? MATCH_EXCERPT_REGEXP.test(commentBody) : false;
57
55
  if (isCMSExcerptComment) {
58
56
  return false;
59
57
  }
@@ -73,11 +71,45 @@ function isMatch(input, matcher) {
73
71
  }
74
72
  return false;
75
73
  }
74
+ function normalizeRemoveType(removeType) {
75
+ if (removeType === 'all' || removeType === 'safe' || isMatcher(removeType)) {
76
+ return removeType;
77
+ }
78
+ if (typeof removeType === 'string') {
79
+ const regexp = parseRegexString(removeType);
80
+ if (regexp) {
81
+ return regexp;
82
+ }
83
+ }
84
+ return 'safe';
85
+ }
76
86
  function isMatcher(matcher) {
77
87
  if (matcher instanceof RegExp || typeof matcher === 'function') {
78
88
  return true;
79
89
  }
80
90
  return false;
81
91
  }
92
+ function parseRegexString(value) {
93
+ const literalMatch = value.match(/^\/([\s\S]+)\/([gimsuy]*)$/);
94
+ if (literalMatch) {
95
+ return tryCreateRegExp(literalMatch[1], literalMatch[2]);
96
+ }
97
+ return tryCreateRegExp(value);
98
+ }
99
+ function tryCreateRegExp(pattern, flags) {
100
+ try {
101
+ return new RegExp(pattern, flags);
102
+ } catch (unused) {
103
+ return null;
104
+ }
105
+ }
106
+ function getCommentBody(text) {
107
+ const trimmed = text.trim();
108
+ const match = trimmed.match(/^<!--([\s\S]*?)-->$/);
109
+ if (!match) {
110
+ return null;
111
+ }
112
+ return match[1];
113
+ }
82
114
 
83
115
  exports.default = mod;
@@ -1,11 +1,11 @@
1
1
  import { isComment, isConditionalComment } from '../helpers.mjs';
2
2
 
3
- const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
3
+ const MATCH_EXCERPT_REGEXP = /^\s*more\b/i;
4
+ const MATCH_NOINDEX_REGEXP = /^\s*\/?\s*noindex\s*$/i;
5
+ const MATCH_SSE_REGEXP = /^\s*\/?\s*sse\s*$/i;
4
6
  /** Removes HTML comments */ const mod = {
5
7
  onNode (_, removeType) {
6
- if (removeType !== 'all' && removeType !== 'safe' && !isMatcher(removeType)) {
7
- removeType = 'safe';
8
- }
8
+ removeType = normalizeRemoveType(removeType);
9
9
  return (node)=>{
10
10
  if (isCommentToRemove(node, removeType)) {
11
11
  return '';
@@ -14,9 +14,7 @@ const MATCH_EXCERPT_REGEXP = /<!-- ?more ?-->/i;
14
14
  };
15
15
  },
16
16
  onContent (_, removeType) {
17
- if (removeType !== 'all' && removeType !== 'safe' && !isMatcher(removeType)) {
18
- removeType = 'safe';
19
- }
17
+ removeType = normalizeRemoveType(removeType);
20
18
  return (contents)=>{
21
19
  return contents.filter((content)=>!isCommentToRemove(content, removeType));
22
20
  };
@@ -31,13 +29,13 @@ function isCommentToRemove(text, removeType) {
31
29
  return false;
32
30
  }
33
31
  if (removeType === 'safe') {
34
- const isNoindex = text === '<!--noindex-->' || text === '<!--/noindex-->';
35
- // Don't remove noindex comments.
36
- // See: https://yandex.com/support/webmaster/controlling-robot/html.xml
32
+ const commentBody = getCommentBody(text);
33
+ const isNoindex = commentBody ? MATCH_NOINDEX_REGEXP.test(commentBody) : false;
34
+ // Don't remove noindex comments. It was used by some search engines in the past.
37
35
  if (isNoindex) {
38
36
  return false;
39
37
  }
40
- const isServerSideExclude = text === '<!--sse-->' || text === '<!--/sse-->';
38
+ const isServerSideExclude = commentBody ? MATCH_SSE_REGEXP.test(commentBody) : false;
41
39
  // Don't remove sse comments.
42
40
  // See: https://support.cloudflare.com/hc/en-us/articles/200170036-What-does-Server-Side-Excludes-SSE-do-
43
41
  if (isServerSideExclude) {
@@ -51,7 +49,7 @@ function isCommentToRemove(text, removeType) {
51
49
  // Hugo: https://gohugo.io/content-management/summaries/#manual-summary-splitting
52
50
  // WordPress: https://wordpress.com/support/wordpress-editor/blocks/more-block/2/
53
51
  // Jekyll: https://jekyllrb.com/docs/posts/#post-excerpts
54
- const isCMSExcerptComment = MATCH_EXCERPT_REGEXP.test(text);
52
+ const isCMSExcerptComment = commentBody ? MATCH_EXCERPT_REGEXP.test(commentBody) : false;
55
53
  if (isCMSExcerptComment) {
56
54
  return false;
57
55
  }
@@ -71,11 +69,45 @@ function isMatch(input, matcher) {
71
69
  }
72
70
  return false;
73
71
  }
72
+ function normalizeRemoveType(removeType) {
73
+ if (removeType === 'all' || removeType === 'safe' || isMatcher(removeType)) {
74
+ return removeType;
75
+ }
76
+ if (typeof removeType === 'string') {
77
+ const regexp = parseRegexString(removeType);
78
+ if (regexp) {
79
+ return regexp;
80
+ }
81
+ }
82
+ return 'safe';
83
+ }
74
84
  function isMatcher(matcher) {
75
85
  if (matcher instanceof RegExp || typeof matcher === 'function') {
76
86
  return true;
77
87
  }
78
88
  return false;
79
89
  }
90
+ function parseRegexString(value) {
91
+ const literalMatch = value.match(/^\/([\s\S]+)\/([gimsuy]*)$/);
92
+ if (literalMatch) {
93
+ return tryCreateRegExp(literalMatch[1], literalMatch[2]);
94
+ }
95
+ return tryCreateRegExp(value);
96
+ }
97
+ function tryCreateRegExp(pattern, flags) {
98
+ try {
99
+ return new RegExp(pattern, flags);
100
+ } catch (unused) {
101
+ return null;
102
+ }
103
+ }
104
+ function getCommentBody(text) {
105
+ const trimmed = text.trim();
106
+ const match = trimmed.match(/^<!--([\s\S]*?)-->$/);
107
+ if (!match) {
108
+ return null;
109
+ }
110
+ return match[1];
111
+ }
80
112
 
81
113
  export { mod as default };