htmlnano 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +18 -17
  2. package/dist/_modules/collapseAttributeWhitespace.d.mts +41 -5
  3. package/dist/_modules/collapseAttributeWhitespace.d.ts +41 -5
  4. package/dist/_modules/collapseAttributeWhitespace.js +69 -16
  5. package/dist/_modules/collapseAttributeWhitespace.mjs +67 -17
  6. package/dist/_modules/collapseBooleanAttributes.d.mts +36 -3
  7. package/dist/_modules/collapseBooleanAttributes.d.ts +36 -3
  8. package/dist/_modules/collapseBooleanAttributes.js +18 -11
  9. package/dist/_modules/collapseBooleanAttributes.mjs +18 -11
  10. package/dist/_modules/collapseWhitespace.d.mts +36 -3
  11. package/dist/_modules/collapseWhitespace.d.ts +36 -3
  12. package/dist/_modules/collapseWhitespace.js +25 -2
  13. package/dist/_modules/collapseWhitespace.mjs +25 -2
  14. package/dist/_modules/custom.d.mts +36 -3
  15. package/dist/_modules/custom.d.ts +36 -3
  16. package/dist/_modules/deduplicateAttributeValues.d.mts +36 -3
  17. package/dist/_modules/deduplicateAttributeValues.d.ts +36 -3
  18. package/dist/_modules/deduplicateAttributeValues.js +20 -5
  19. package/dist/_modules/deduplicateAttributeValues.mjs +21 -6
  20. package/dist/_modules/example.d.mts +36 -3
  21. package/dist/_modules/example.d.ts +36 -3
  22. package/dist/_modules/mergeScripts.d.mts +36 -3
  23. package/dist/_modules/mergeScripts.d.ts +36 -3
  24. package/dist/_modules/mergeScripts.js +99 -24
  25. package/dist/_modules/mergeScripts.mjs +99 -24
  26. package/dist/_modules/mergeStyles.d.mts +36 -3
  27. package/dist/_modules/mergeStyles.d.ts +36 -3
  28. package/dist/_modules/mergeStyles.js +56 -4
  29. package/dist/_modules/mergeStyles.mjs +56 -4
  30. package/dist/_modules/minifyAttributes.d.mts +95 -0
  31. package/dist/_modules/minifyAttributes.d.ts +95 -0
  32. package/dist/_modules/minifyAttributes.js +159 -0
  33. package/dist/_modules/minifyAttributes.mjs +157 -0
  34. package/dist/_modules/minifyConditionalComments.d.mts +36 -3
  35. package/dist/_modules/minifyConditionalComments.d.ts +36 -3
  36. package/dist/_modules/minifyConditionalComments.js +37 -19
  37. package/dist/_modules/minifyConditionalComments.mjs +37 -19
  38. package/dist/_modules/minifyCss.d.mts +36 -3
  39. package/dist/_modules/minifyCss.d.ts +36 -3
  40. package/dist/_modules/minifyCss.js +13 -27
  41. package/dist/_modules/minifyCss.mjs +14 -28
  42. package/dist/_modules/minifyHtmlTemplate.d.mts +91 -0
  43. package/dist/_modules/minifyHtmlTemplate.d.ts +91 -0
  44. package/dist/_modules/minifyHtmlTemplate.js +231 -0
  45. package/dist/_modules/minifyHtmlTemplate.mjs +228 -0
  46. package/dist/_modules/minifyJs.d.mts +36 -3
  47. package/dist/_modules/minifyJs.d.ts +36 -3
  48. package/dist/_modules/minifyJs.js +94 -5
  49. package/dist/_modules/minifyJs.mjs +95 -6
  50. package/dist/_modules/minifyJson.d.mts +36 -3
  51. package/dist/_modules/minifyJson.d.ts +36 -3
  52. package/dist/_modules/minifyJson.js +8 -11
  53. package/dist/_modules/minifyJson.mjs +8 -11
  54. package/dist/_modules/minifySvg.d.mts +36 -3
  55. package/dist/_modules/minifySvg.d.ts +36 -3
  56. package/dist/_modules/minifySvg.js +35 -4
  57. package/dist/_modules/minifySvg.mjs +35 -4
  58. package/dist/_modules/minifyUrls.d.mts +37 -4
  59. package/dist/_modules/minifyUrls.d.ts +37 -4
  60. package/dist/_modules/minifyUrls.js +52 -27
  61. package/dist/_modules/minifyUrls.mjs +52 -27
  62. package/dist/_modules/normalizeAttributeValues.d.mts +36 -3
  63. package/dist/_modules/normalizeAttributeValues.d.ts +36 -3
  64. package/dist/_modules/normalizeAttributeValues.js +10 -8
  65. package/dist/_modules/normalizeAttributeValues.mjs +10 -8
  66. package/dist/_modules/removeAttributeQuotes.d.mts +40 -4
  67. package/dist/_modules/removeAttributeQuotes.d.ts +40 -4
  68. package/dist/_modules/removeAttributeQuotes.js +9 -4
  69. package/dist/_modules/removeAttributeQuotes.mjs +9 -4
  70. package/dist/_modules/removeComments.d.mts +37 -4
  71. package/dist/_modules/removeComments.d.ts +37 -4
  72. package/dist/_modules/removeComments.js +44 -12
  73. package/dist/_modules/removeComments.mjs +44 -12
  74. package/dist/_modules/removeEmptyAttributes.d.mts +36 -3
  75. package/dist/_modules/removeEmptyAttributes.d.ts +36 -3
  76. package/dist/_modules/removeEmptyAttributes.js +37 -16
  77. package/dist/_modules/removeEmptyAttributes.mjs +37 -16
  78. package/dist/_modules/removeEmptyElements.d.mts +95 -0
  79. package/dist/_modules/removeEmptyElements.d.ts +95 -0
  80. package/dist/_modules/removeEmptyElements.js +90 -0
  81. package/dist/_modules/removeEmptyElements.mjs +88 -0
  82. package/dist/_modules/removeOptionalTags.d.mts +36 -3
  83. package/dist/_modules/removeOptionalTags.d.ts +36 -3
  84. package/dist/_modules/removeOptionalTags.js +39 -28
  85. package/dist/_modules/removeOptionalTags.mjs +39 -28
  86. package/dist/_modules/removeRedundantAttributes.d.mts +36 -3
  87. package/dist/_modules/removeRedundantAttributes.d.ts +36 -3
  88. package/dist/_modules/removeRedundantAttributes.js +43 -28
  89. package/dist/_modules/removeRedundantAttributes.mjs +43 -28
  90. package/dist/_modules/removeUnusedCss.d.mts +37 -3
  91. package/dist/_modules/removeUnusedCss.d.ts +37 -3
  92. package/dist/_modules/removeUnusedCss.js +38 -13
  93. package/dist/_modules/removeUnusedCss.mjs +39 -14
  94. package/dist/_modules/sortAttributes.d.mts +36 -3
  95. package/dist/_modules/sortAttributes.d.ts +36 -3
  96. package/dist/_modules/sortAttributes.js +23 -5
  97. package/dist/_modules/sortAttributes.mjs +23 -5
  98. package/dist/_modules/sortAttributesWithLists.d.mts +36 -3
  99. package/dist/_modules/sortAttributesWithLists.d.ts +36 -3
  100. package/dist/_modules/sortAttributesWithLists.js +30 -8
  101. package/dist/_modules/sortAttributesWithLists.mjs +31 -9
  102. package/dist/helpers.d.ts +8 -1
  103. package/dist/helpers.js +48 -0
  104. package/dist/helpers.mjs +45 -1
  105. package/dist/index.d.ts +37 -4
  106. package/dist/index.js +13 -0
  107. package/dist/index.mjs +13 -0
  108. package/dist/presets/ampSafe.d.ts +36 -3
  109. package/dist/presets/max.d.ts +36 -3
  110. package/dist/presets/max.js +4 -0
  111. package/dist/presets/max.mjs +4 -0
  112. package/dist/presets/safe.d.ts +36 -3
  113. package/dist/presets/safe.js +6 -0
  114. package/dist/presets/safe.mjs +6 -0
  115. package/package.json +21 -13
@@ -2,19 +2,33 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
4
4
 
5
+ const caseInsensitiveListAttributes = new Set([
6
+ 'rel',
7
+ 'sandbox',
8
+ 'dropzone',
9
+ 'sizes'
10
+ ]);
11
+ function getDeduplicationKey(attrNameLower, attrValue) {
12
+ if (!caseInsensitiveListAttributes.has(attrNameLower)) {
13
+ return attrValue;
14
+ }
15
+ return attrValue.toLowerCase();
16
+ }
5
17
  /** Deduplicate values inside list-like attributes (e.g. class, rel) */ const mod = {
6
18
  onAttrs () {
7
- return (attrs)=>{
19
+ return (attrs, node)=>{
8
20
  const newAttrs = attrs;
21
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
9
22
  Object.keys(attrs).forEach((attrName)=>{
10
- if (!collapseAttributeWhitespace_js.attributesWithLists.has(attrName)) {
23
+ const attrNameLower = attrName.toLowerCase();
24
+ if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
11
25
  return;
12
26
  }
13
27
  if (typeof attrs[attrName] !== 'string') {
14
28
  return;
15
29
  }
16
30
  const attrValues = attrs[attrName].split(/\s/);
17
- const uniqeAttrValues = new Set();
31
+ const uniqueAttrValues = new Set();
18
32
  const deduplicatedAttrValues = [];
19
33
  attrValues.forEach((attrValue)=>{
20
34
  if (!attrValue) {
@@ -22,11 +36,12 @@ var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js')
22
36
  deduplicatedAttrValues.push('');
23
37
  return;
24
38
  }
25
- if (uniqeAttrValues.has(attrValue)) {
39
+ const deduplicationKey = getDeduplicationKey(attrNameLower, attrValue);
40
+ if (uniqueAttrValues.has(deduplicationKey)) {
26
41
  return;
27
42
  }
28
43
  deduplicatedAttrValues.push(attrValue);
29
- uniqeAttrValues.add(attrValue);
44
+ uniqueAttrValues.add(deduplicationKey);
30
45
  });
31
46
  newAttrs[attrName] = deduplicatedAttrValues.join(' ');
32
47
  });
@@ -1,18 +1,32 @@
1
- import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
1
+ import { isListAttribute } from './collapseAttributeWhitespace.mjs';
2
2
 
3
+ const caseInsensitiveListAttributes = new Set([
4
+ 'rel',
5
+ 'sandbox',
6
+ 'dropzone',
7
+ 'sizes'
8
+ ]);
9
+ function getDeduplicationKey(attrNameLower, attrValue) {
10
+ if (!caseInsensitiveListAttributes.has(attrNameLower)) {
11
+ return attrValue;
12
+ }
13
+ return attrValue.toLowerCase();
14
+ }
3
15
  /** Deduplicate values inside list-like attributes (e.g. class, rel) */ const mod = {
4
16
  onAttrs () {
5
- return (attrs)=>{
17
+ return (attrs, node)=>{
6
18
  const newAttrs = attrs;
19
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
7
20
  Object.keys(attrs).forEach((attrName)=>{
8
- if (!attributesWithLists.has(attrName)) {
21
+ const attrNameLower = attrName.toLowerCase();
22
+ if (!isListAttribute(attrNameLower, tagName)) {
9
23
  return;
10
24
  }
11
25
  if (typeof attrs[attrName] !== 'string') {
12
26
  return;
13
27
  }
14
28
  const attrValues = attrs[attrName].split(/\s/);
15
- const uniqeAttrValues = new Set();
29
+ const uniqueAttrValues = new Set();
16
30
  const deduplicatedAttrValues = [];
17
31
  attrValues.forEach((attrValue)=>{
18
32
  if (!attrValue) {
@@ -20,11 +34,12 @@ import { attributesWithLists } from './collapseAttributeWhitespace.mjs';
20
34
  deduplicatedAttrValues.push('');
21
35
  return;
22
36
  }
23
- if (uniqeAttrValues.has(attrValue)) {
37
+ const deduplicationKey = getDeduplicationKey(attrNameLower, attrValue);
38
+ if (uniqueAttrValues.has(deduplicationKey)) {
24
39
  return;
25
40
  }
26
41
  deduplicatedAttrValues.push(attrValue);
27
- uniqeAttrValues.add(attrValue);
42
+ uniqueAttrValues.add(deduplicationKey);
28
43
  });
29
44
  newAttrs[attrName] = deduplicatedAttrValues.join(' ');
30
45
  });
@@ -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
  }
@@ -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
  }
@@ -2,50 +2,125 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var helpers_js = require('../helpers.js');
4
4
 
5
+ function normalizeAsyncAttr(attrs) {
6
+ if (!attrs) {
7
+ return;
8
+ }
9
+ if (attrs.async === '') {
10
+ attrs.async = true;
11
+ }
12
+ if (attrs.nomodule === '') {
13
+ attrs.nomodule = true;
14
+ }
15
+ }
16
+ function getScriptType(attrs) {
17
+ const type = attrs.type || 'text/javascript';
18
+ return typeof type === 'string' ? type.toLowerCase() : 'text/javascript';
19
+ }
20
+ function isMergeableScriptType(type) {
21
+ return type === 'text/javascript' || type === 'application/javascript';
22
+ }
23
+ const booleanAttrs = new Set([
24
+ 'async',
25
+ 'defer',
26
+ 'nomodule'
27
+ ]);
28
+ function normalizeScriptAttrsForKey(attrs, scriptType) {
29
+ const normalized = {
30
+ type: scriptType
31
+ };
32
+ for (const [key, value] of Object.entries(attrs)){
33
+ if (key === 'src' || key === 'integrity' || key === 'type') {
34
+ continue;
35
+ }
36
+ if (value === undefined) {
37
+ continue;
38
+ }
39
+ if (booleanAttrs.has(key)) {
40
+ normalized[key] = true;
41
+ continue;
42
+ }
43
+ normalized[key] = value;
44
+ }
45
+ return normalized;
46
+ }
47
+ function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
48
+ const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
49
+ const keyObject = {
50
+ index: scriptSrcIndex,
51
+ ...normalizedAttrs
52
+ };
53
+ const sortedKeys = Object.keys(keyObject).sort();
54
+ const sortedKeyObject = {};
55
+ for (const key of sortedKeys){
56
+ sortedKeyObject[key] = keyObject[key];
57
+ }
58
+ return JSON.stringify(sortedKeyObject);
59
+ }
60
+ function endsWithLineComment(scriptContent) {
61
+ const lastNewlineIndex = Math.max(scriptContent.lastIndexOf('\n'), scriptContent.lastIndexOf('\r'));
62
+ const lastLine = lastNewlineIndex === -1 ? scriptContent : scriptContent.slice(lastNewlineIndex + 1);
63
+ return /\/\/.*$/.test(lastLine);
64
+ }
65
+ function mergeScriptNodes(scriptNodesIndex, tracking) {
66
+ for (const scriptNodes of Object.values(scriptNodesIndex)){
67
+ if (scriptNodes.length < 2) {
68
+ continue;
69
+ }
70
+ const lastScriptNode = scriptNodes.pop();
71
+ tracking.mergedScriptNodes.add(lastScriptNode);
72
+ scriptNodes.reverse().forEach((scriptNode)=>{
73
+ let scriptContent = helpers_js.extractTextContentFromNode(scriptNode).trim();
74
+ if (!scriptContent) {
75
+ tracking.removedScriptNodes.add(scriptNode);
76
+ // @ts-expect-error -- remove node
77
+ scriptNode.tag = false;
78
+ scriptNode.content = [];
79
+ return;
80
+ }
81
+ if (endsWithLineComment(scriptContent)) {
82
+ scriptContent += '\n;';
83
+ } else if (scriptContent.slice(-1) !== ';') {
84
+ scriptContent += ';';
85
+ }
86
+ lastScriptNode.content = lastScriptNode.content || [];
87
+ lastScriptNode.content.unshift(scriptContent);
88
+ tracking.removedScriptNodes.add(scriptNode);
89
+ // @ts-expect-error -- remove node
90
+ scriptNode.tag = false;
91
+ scriptNode.content = [];
92
+ });
93
+ }
94
+ }
5
95
  /* Merge multiple <script> into one */ const mod = {
6
96
  default (tree) {
7
97
  const scriptNodesIndex = {};
98
+ const tracking = {
99
+ mergedScriptNodes: new WeakSet(),
100
+ removedScriptNodes: new WeakSet()
101
+ };
8
102
  let scriptSrcIndex = 1;
9
103
  tree.match({
10
104
  tag: 'script'
11
105
  }, (node)=>{
12
106
  const nodeAttrs = node.attrs || {};
107
+ normalizeAsyncAttr(nodeAttrs);
13
108
  if ('src' in nodeAttrs || 'integrity' in nodeAttrs) {
14
109
  scriptSrcIndex++;
15
110
  return node;
16
111
  }
17
- const scriptType = nodeAttrs.type || 'text/javascript';
18
- if (scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
112
+ const scriptType = getScriptType(nodeAttrs);
113
+ if (!isMergeableScriptType(scriptType)) {
19
114
  return node;
20
115
  }
21
- const scriptKey = JSON.stringify({
22
- id: nodeAttrs.id,
23
- class: nodeAttrs.class,
24
- type: scriptType,
25
- defer: nodeAttrs.defer !== undefined,
26
- async: nodeAttrs.async !== undefined,
27
- index: scriptSrcIndex
28
- });
116
+ const scriptKey = buildScriptKey(nodeAttrs, scriptType, scriptSrcIndex);
29
117
  if (!scriptNodesIndex[scriptKey]) {
30
118
  scriptNodesIndex[scriptKey] = [];
31
119
  }
32
120
  scriptNodesIndex[scriptKey].push(node);
33
121
  return node;
34
122
  });
35
- for (const scriptNodes of Object.values(scriptNodesIndex)){
36
- const lastScriptNode = scriptNodes.pop();
37
- scriptNodes.reverse().forEach((scriptNode)=>{
38
- let scriptContent = helpers_js.extractTextContentFromNode(scriptNode).trim();
39
- if (scriptContent.slice(-1) !== ';') {
40
- scriptContent += ';';
41
- }
42
- lastScriptNode.content = lastScriptNode.content || [];
43
- lastScriptNode.content.unshift(scriptContent);
44
- // @ts-expect-error -- remove node
45
- scriptNode.tag = false;
46
- scriptNode.content = [];
47
- });
48
- }
123
+ mergeScriptNodes(scriptNodesIndex, tracking);
49
124
  return tree;
50
125
  }
51
126
  };
@@ -1,49 +1,124 @@
1
1
  import { extractTextContentFromNode } from '../helpers.mjs';
2
2
 
3
+ function normalizeAsyncAttr(attrs) {
4
+ if (!attrs) {
5
+ return;
6
+ }
7
+ if (attrs.async === '') {
8
+ attrs.async = true;
9
+ }
10
+ if (attrs.nomodule === '') {
11
+ attrs.nomodule = true;
12
+ }
13
+ }
14
+ function getScriptType(attrs) {
15
+ const type = attrs.type || 'text/javascript';
16
+ return typeof type === 'string' ? type.toLowerCase() : 'text/javascript';
17
+ }
18
+ function isMergeableScriptType(type) {
19
+ return type === 'text/javascript' || type === 'application/javascript';
20
+ }
21
+ const booleanAttrs = new Set([
22
+ 'async',
23
+ 'defer',
24
+ 'nomodule'
25
+ ]);
26
+ function normalizeScriptAttrsForKey(attrs, scriptType) {
27
+ const normalized = {
28
+ type: scriptType
29
+ };
30
+ for (const [key, value] of Object.entries(attrs)){
31
+ if (key === 'src' || key === 'integrity' || key === 'type') {
32
+ continue;
33
+ }
34
+ if (value === undefined) {
35
+ continue;
36
+ }
37
+ if (booleanAttrs.has(key)) {
38
+ normalized[key] = true;
39
+ continue;
40
+ }
41
+ normalized[key] = value;
42
+ }
43
+ return normalized;
44
+ }
45
+ function buildScriptKey(attrs, scriptType, scriptSrcIndex) {
46
+ const normalizedAttrs = normalizeScriptAttrsForKey(attrs, scriptType);
47
+ const keyObject = {
48
+ index: scriptSrcIndex,
49
+ ...normalizedAttrs
50
+ };
51
+ const sortedKeys = Object.keys(keyObject).sort();
52
+ const sortedKeyObject = {};
53
+ for (const key of sortedKeys){
54
+ sortedKeyObject[key] = keyObject[key];
55
+ }
56
+ return JSON.stringify(sortedKeyObject);
57
+ }
58
+ function endsWithLineComment(scriptContent) {
59
+ const lastNewlineIndex = Math.max(scriptContent.lastIndexOf('\n'), scriptContent.lastIndexOf('\r'));
60
+ const lastLine = lastNewlineIndex === -1 ? scriptContent : scriptContent.slice(lastNewlineIndex + 1);
61
+ return /\/\/.*$/.test(lastLine);
62
+ }
63
+ function mergeScriptNodes(scriptNodesIndex, tracking) {
64
+ for (const scriptNodes of Object.values(scriptNodesIndex)){
65
+ if (scriptNodes.length < 2) {
66
+ continue;
67
+ }
68
+ const lastScriptNode = scriptNodes.pop();
69
+ tracking.mergedScriptNodes.add(lastScriptNode);
70
+ scriptNodes.reverse().forEach((scriptNode)=>{
71
+ let scriptContent = extractTextContentFromNode(scriptNode).trim();
72
+ if (!scriptContent) {
73
+ tracking.removedScriptNodes.add(scriptNode);
74
+ // @ts-expect-error -- remove node
75
+ scriptNode.tag = false;
76
+ scriptNode.content = [];
77
+ return;
78
+ }
79
+ if (endsWithLineComment(scriptContent)) {
80
+ scriptContent += '\n;';
81
+ } else if (scriptContent.slice(-1) !== ';') {
82
+ scriptContent += ';';
83
+ }
84
+ lastScriptNode.content = lastScriptNode.content || [];
85
+ lastScriptNode.content.unshift(scriptContent);
86
+ tracking.removedScriptNodes.add(scriptNode);
87
+ // @ts-expect-error -- remove node
88
+ scriptNode.tag = false;
89
+ scriptNode.content = [];
90
+ });
91
+ }
92
+ }
3
93
  /* Merge multiple <script> into one */ const mod = {
4
94
  default (tree) {
5
95
  const scriptNodesIndex = {};
96
+ const tracking = {
97
+ mergedScriptNodes: new WeakSet(),
98
+ removedScriptNodes: new WeakSet()
99
+ };
6
100
  let scriptSrcIndex = 1;
7
101
  tree.match({
8
102
  tag: 'script'
9
103
  }, (node)=>{
10
104
  const nodeAttrs = node.attrs || {};
105
+ normalizeAsyncAttr(nodeAttrs);
11
106
  if ('src' in nodeAttrs || 'integrity' in nodeAttrs) {
12
107
  scriptSrcIndex++;
13
108
  return node;
14
109
  }
15
- const scriptType = nodeAttrs.type || 'text/javascript';
16
- if (scriptType !== 'text/javascript' && scriptType !== 'application/javascript') {
110
+ const scriptType = getScriptType(nodeAttrs);
111
+ if (!isMergeableScriptType(scriptType)) {
17
112
  return node;
18
113
  }
19
- const scriptKey = JSON.stringify({
20
- id: nodeAttrs.id,
21
- class: nodeAttrs.class,
22
- type: scriptType,
23
- defer: nodeAttrs.defer !== undefined,
24
- async: nodeAttrs.async !== undefined,
25
- index: scriptSrcIndex
26
- });
114
+ const scriptKey = buildScriptKey(nodeAttrs, scriptType, scriptSrcIndex);
27
115
  if (!scriptNodesIndex[scriptKey]) {
28
116
  scriptNodesIndex[scriptKey] = [];
29
117
  }
30
118
  scriptNodesIndex[scriptKey].push(node);
31
119
  return node;
32
120
  });
33
- for (const scriptNodes of Object.values(scriptNodesIndex)){
34
- const lastScriptNode = scriptNodes.pop();
35
- scriptNodes.reverse().forEach((scriptNode)=>{
36
- let scriptContent = extractTextContentFromNode(scriptNode).trim();
37
- if (scriptContent.slice(-1) !== ';') {
38
- scriptContent += ';';
39
- }
40
- lastScriptNode.content = lastScriptNode.content || [];
41
- lastScriptNode.content.unshift(scriptContent);
42
- // @ts-expect-error -- remove node
43
- scriptNode.tag = false;
44
- scriptNode.content = [];
45
- });
46
- }
121
+ mergeScriptNodes(scriptNodesIndex, tracking);
47
122
  return tree;
48
123
  }
49
124
  };