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
@@ -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
  }
@@ -67,22 +67,36 @@ const safeToRemoveAttrs = {
67
67
  'object',
68
68
  'video'
69
69
  ],
70
- high: 'meter',
71
- href: 'link',
72
- list: 'input',
73
- low: 'meter',
74
- manifest: 'html',
70
+ high: [
71
+ 'meter'
72
+ ],
73
+ href: [
74
+ 'link'
75
+ ],
76
+ list: [
77
+ 'input'
78
+ ],
79
+ low: [
80
+ 'meter'
81
+ ],
82
+ manifest: [
83
+ 'html'
84
+ ],
75
85
  max: [
76
86
  'meter',
77
87
  'progress'
78
88
  ],
79
- maxLength: [
89
+ maxlength: [
80
90
  'input',
81
91
  'textarea'
82
92
  ],
83
- menu: 'button',
84
- min: 'meter',
85
- minLength: [
93
+ menu: [
94
+ 'button'
95
+ ],
96
+ min: [
97
+ 'meter'
98
+ ],
99
+ minlength: [
86
100
  'input',
87
101
  'textarea'
88
102
  ],
@@ -119,7 +133,9 @@ const safeToRemoveAttrs = {
119
133
  'area',
120
134
  'link'
121
135
  ],
122
- rows: 'textarea',
136
+ rows: [
137
+ 'textarea'
138
+ ],
123
139
  rowspan: [
124
140
  'td',
125
141
  'th'
@@ -143,7 +159,9 @@ const safeToRemoveAttrs = {
143
159
  'track',
144
160
  'video'
145
161
  ],
146
- start: 'ol',
162
+ start: [
163
+ 'ol'
164
+ ],
147
165
  tabindex: null,
148
166
  type: [
149
167
  'a',
@@ -180,12 +198,15 @@ const mod = {
180
198
  const newAttrs = {
181
199
  ...attrs
182
200
  };
201
+ const tagName = typeof node.tag === 'string' ? node.tag.toLowerCase() : '';
183
202
  Object.entries(attrs).forEach(([attrName, attrValue])=>{
184
- if (helpers_js.isEventHandler(attrName) || Object.hasOwnProperty.call(safeToRemoveAttrs, attrName) && (safeToRemoveAttrs[attrName] === null || typeof node.tag === 'string' && safeToRemoveAttrs[attrName].includes(node.tag))) {
185
- if (typeof attrValue === 'string') {
186
- if (attrValue === '' || attrValue.trim() === '') {
187
- delete newAttrs[attrName];
188
- }
203
+ const normalizedAttrName = attrName.toLowerCase();
204
+ const safeAttr = safeToRemoveAttrs[normalizedAttrName];
205
+ if (helpers_js.isEventHandler(attrName) || safeAttr !== undefined && (safeAttr === null || tagName && safeAttr.includes(tagName))) {
206
+ if (attrValue === null || attrValue === undefined) {
207
+ delete newAttrs[attrName];
208
+ } else if (typeof attrValue === 'string' && attrValue.trim() === '') {
209
+ delete newAttrs[attrName];
189
210
  }
190
211
  }
191
212
  });
@@ -65,22 +65,36 @@ const safeToRemoveAttrs = {
65
65
  'object',
66
66
  'video'
67
67
  ],
68
- high: 'meter',
69
- href: 'link',
70
- list: 'input',
71
- low: 'meter',
72
- manifest: 'html',
68
+ high: [
69
+ 'meter'
70
+ ],
71
+ href: [
72
+ 'link'
73
+ ],
74
+ list: [
75
+ 'input'
76
+ ],
77
+ low: [
78
+ 'meter'
79
+ ],
80
+ manifest: [
81
+ 'html'
82
+ ],
73
83
  max: [
74
84
  'meter',
75
85
  'progress'
76
86
  ],
77
- maxLength: [
87
+ maxlength: [
78
88
  'input',
79
89
  'textarea'
80
90
  ],
81
- menu: 'button',
82
- min: 'meter',
83
- minLength: [
91
+ menu: [
92
+ 'button'
93
+ ],
94
+ min: [
95
+ 'meter'
96
+ ],
97
+ minlength: [
84
98
  'input',
85
99
  'textarea'
86
100
  ],
@@ -117,7 +131,9 @@ const safeToRemoveAttrs = {
117
131
  'area',
118
132
  'link'
119
133
  ],
120
- rows: 'textarea',
134
+ rows: [
135
+ 'textarea'
136
+ ],
121
137
  rowspan: [
122
138
  'td',
123
139
  'th'
@@ -141,7 +157,9 @@ const safeToRemoveAttrs = {
141
157
  'track',
142
158
  'video'
143
159
  ],
144
- start: 'ol',
160
+ start: [
161
+ 'ol'
162
+ ],
145
163
  tabindex: null,
146
164
  type: [
147
165
  'a',
@@ -178,12 +196,15 @@ const mod = {
178
196
  const newAttrs = {
179
197
  ...attrs
180
198
  };
199
+ const tagName = typeof node.tag === 'string' ? node.tag.toLowerCase() : '';
181
200
  Object.entries(attrs).forEach(([attrName, attrValue])=>{
182
- if (isEventHandler(attrName) || Object.hasOwnProperty.call(safeToRemoveAttrs, attrName) && (safeToRemoveAttrs[attrName] === null || typeof node.tag === 'string' && safeToRemoveAttrs[attrName].includes(node.tag))) {
183
- if (typeof attrValue === 'string') {
184
- if (attrValue === '' || attrValue.trim() === '') {
185
- delete newAttrs[attrName];
186
- }
201
+ const normalizedAttrName = attrName.toLowerCase();
202
+ const safeAttr = safeToRemoveAttrs[normalizedAttrName];
203
+ if (isEventHandler(attrName) || safeAttr !== undefined && (safeAttr === null || tagName && safeAttr.includes(tagName))) {
204
+ if (attrValue === null || attrValue === undefined) {
205
+ delete newAttrs[attrName];
206
+ } else if (typeof attrValue === 'string' && attrValue.trim() === '') {
207
+ delete newAttrs[attrName];
187
208
  }
188
209
  }
189
210
  });
@@ -0,0 +1,95 @@
1
+ import PostHTML from 'posthtml';
2
+ import { MinifyOptions } from 'terser';
3
+ import { Options } from 'cssnano';
4
+ import { Config } from 'svgo';
5
+ import { UserDefinedOptions } from 'purgecss';
6
+
7
+ type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
8
+ options?: {
9
+ quoteAllAttributes?: boolean | undefined;
10
+ quoteStyle?: 0 | 1 | 2 | undefined;
11
+ replaceQuote?: boolean | undefined;
12
+ } | undefined;
13
+ render(): string;
14
+ render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
15
+ };
16
+ type MaybeArray<T> = T | Array<T>;
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[];
23
+ interface HtmlnanoOptions {
24
+ skipConfigLoading?: boolean;
25
+ configPath?: string;
26
+ skipInternalWarnings?: boolean;
27
+ collapseAttributeWhitespace?: boolean;
28
+ collapseBooleanAttributes?: {
29
+ amphtml?: boolean;
30
+ };
31
+ collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
32
+ custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
33
+ deduplicateAttributeValues?: boolean;
34
+ minifyUrls?: URL | string | false;
35
+ mergeStyles?: boolean;
36
+ mergeScripts?: boolean;
37
+ minifyCss?: Options | boolean;
38
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
+ minifyConditionalComments?: boolean;
40
+ minifyJs?: MinifyOptions | boolean;
41
+ minifyJson?: boolean;
42
+ minifyAttributes?: boolean | {
43
+ metaContent?: boolean;
44
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
45
+ };
46
+ minifySvg?: Config | boolean;
47
+ normalizeAttributeValues?: boolean;
48
+ removeAttributeQuotes?: boolean | {
49
+ force?: boolean;
50
+ };
51
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
52
+ removeEmptyAttributes?: boolean;
53
+ removeEmptyElements?: boolean | {
54
+ removeWithAttributes?: boolean;
55
+ };
56
+ removeRedundantAttributes?: boolean;
57
+ removeOptionalTags?: 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
+ };
74
+ sortAttributes?: boolean | 'alphabetical' | 'frequency';
75
+ sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
76
+ }
77
+ type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
+ type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
+ type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
80
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
81
+ type HtmlnanoModule<Options = any> = {
82
+ onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
+ onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
84
+ onNode?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleNodeHandler;
85
+ default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
86
+ };
87
+
88
+ interface RemoveEmptyElementsOptions {
89
+ removeWithAttributes?: boolean;
90
+ }
91
+ type RemoveEmptyElementsConfig = boolean | RemoveEmptyElementsOptions;
92
+ declare const mod: HtmlnanoModule<RemoveEmptyElementsConfig>;
93
+
94
+ export { mod as default };
95
+ export type { RemoveEmptyElementsOptions };
@@ -0,0 +1,95 @@
1
+ import PostHTML from 'posthtml';
2
+ import { MinifyOptions } from 'terser';
3
+ import { Options } from 'cssnano';
4
+ import { Config } from 'svgo';
5
+ import { UserDefinedOptions } from 'purgecss';
6
+
7
+ type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
8
+ options?: {
9
+ quoteAllAttributes?: boolean | undefined;
10
+ quoteStyle?: 0 | 1 | 2 | undefined;
11
+ replaceQuote?: boolean | undefined;
12
+ } | undefined;
13
+ render(): string;
14
+ render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
15
+ };
16
+ type MaybeArray<T> = T | Array<T>;
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[];
23
+ interface HtmlnanoOptions {
24
+ skipConfigLoading?: boolean;
25
+ configPath?: string;
26
+ skipInternalWarnings?: boolean;
27
+ collapseAttributeWhitespace?: boolean;
28
+ collapseBooleanAttributes?: {
29
+ amphtml?: boolean;
30
+ };
31
+ collapseWhitespace?: 'conservative' | 'all' | 'aggressive';
32
+ custom?: MaybeArray<(tree: PostHTMLTreeLike, options?: any) => (PostHTML.Node | PostHTMLTreeLike)>;
33
+ deduplicateAttributeValues?: boolean;
34
+ minifyUrls?: URL | string | false;
35
+ mergeStyles?: boolean;
36
+ mergeScripts?: boolean;
37
+ minifyCss?: Options | boolean;
38
+ minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
+ minifyConditionalComments?: boolean;
40
+ minifyJs?: MinifyOptions | boolean;
41
+ minifyJson?: boolean;
42
+ minifyAttributes?: boolean | {
43
+ metaContent?: boolean;
44
+ redundantWhitespaces?: 'safe' | 'agressive' | false;
45
+ };
46
+ minifySvg?: Config | boolean;
47
+ normalizeAttributeValues?: boolean;
48
+ removeAttributeQuotes?: boolean | {
49
+ force?: boolean;
50
+ };
51
+ removeComments?: boolean | RegExp | ((comment: string) => boolean) | string;
52
+ removeEmptyAttributes?: boolean;
53
+ removeEmptyElements?: boolean | {
54
+ removeWithAttributes?: boolean;
55
+ };
56
+ removeRedundantAttributes?: boolean;
57
+ removeOptionalTags?: 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
+ };
74
+ sortAttributes?: boolean | 'alphabetical' | 'frequency';
75
+ sortAttributesWithLists?: boolean | 'alphabetical' | 'frequency';
76
+ }
77
+ type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
+ type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
+ type HtmlnanoModuleNodeHandler = (node: PostHTMLNodeLike) => PostHTML.Node | string;
80
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends object ? Partial<T> : T;
81
+ type HtmlnanoModule<Options = any> = {
82
+ onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
+ onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
84
+ onNode?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleNodeHandler;
85
+ default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
86
+ };
87
+
88
+ interface RemoveEmptyElementsOptions {
89
+ removeWithAttributes?: boolean;
90
+ }
91
+ type RemoveEmptyElementsConfig = boolean | RemoveEmptyElementsOptions;
92
+ declare const mod: HtmlnanoModule<RemoveEmptyElementsConfig>;
93
+
94
+ export { mod as default };
95
+ export type { RemoveEmptyElementsOptions };
@@ -0,0 +1,90 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var helpers_js = require('../helpers.js');
4
+
5
+ const voidElements = new Set([
6
+ 'area',
7
+ 'base',
8
+ 'br',
9
+ 'col',
10
+ 'embed',
11
+ 'hr',
12
+ 'img',
13
+ 'input',
14
+ 'link',
15
+ 'meta',
16
+ 'param',
17
+ 'source',
18
+ 'track',
19
+ 'wbr'
20
+ ]);
21
+ function normalizeOptions(moduleOptions) {
22
+ if (typeof moduleOptions === 'object' && moduleOptions) {
23
+ return {
24
+ removeWithAttributes: moduleOptions.removeWithAttributes === true
25
+ };
26
+ }
27
+ return {
28
+ removeWithAttributes: false
29
+ };
30
+ }
31
+ function hasAttributes(node) {
32
+ return !!node.attrs && Object.keys(node.attrs).length > 0;
33
+ }
34
+ function isIgnorableText(text) {
35
+ return helpers_js.isComment(text) || text.trim() === '';
36
+ }
37
+ function isEmptyContent(content) {
38
+ if (!content) {
39
+ return true;
40
+ }
41
+ const contentArray = Array.isArray(content) ? content : [
42
+ content
43
+ ];
44
+ if (contentArray.length === 0) {
45
+ return true;
46
+ }
47
+ return contentArray.every((child)=>typeof child === 'string' && isIgnorableText(child));
48
+ }
49
+ function shouldRemoveNode(node, options) {
50
+ if (!node.tag || typeof node.tag !== 'string') {
51
+ return false;
52
+ }
53
+ if (voidElements.has(node.tag.toLowerCase())) {
54
+ return false;
55
+ }
56
+ if (!options.removeWithAttributes && hasAttributes(node)) {
57
+ return false;
58
+ }
59
+ return isEmptyContent(node.content);
60
+ }
61
+ function pruneNodes(nodes, options) {
62
+ const result = [];
63
+ for (const node of nodes){
64
+ if (typeof node === 'string') {
65
+ result.push(node);
66
+ continue;
67
+ }
68
+ if (node.content) {
69
+ const contentArray = Array.isArray(node.content) ? node.content : [
70
+ node.content
71
+ ];
72
+ node.content = pruneNodes(contentArray, options);
73
+ }
74
+ if (shouldRemoveNode(node, options)) {
75
+ continue;
76
+ }
77
+ result.push(node);
78
+ }
79
+ return result;
80
+ }
81
+ const mod = {
82
+ default (tree, _options, moduleOptions) {
83
+ const normalizedOptions = normalizeOptions(moduleOptions);
84
+ const pruned = pruneNodes(tree, normalizedOptions);
85
+ tree.splice(0, tree.length, ...pruned);
86
+ return tree;
87
+ }
88
+ };
89
+
90
+ exports.default = mod;
@@ -0,0 +1,88 @@
1
+ import { isComment } from '../helpers.mjs';
2
+
3
+ const voidElements = new Set([
4
+ 'area',
5
+ 'base',
6
+ 'br',
7
+ 'col',
8
+ 'embed',
9
+ 'hr',
10
+ 'img',
11
+ 'input',
12
+ 'link',
13
+ 'meta',
14
+ 'param',
15
+ 'source',
16
+ 'track',
17
+ 'wbr'
18
+ ]);
19
+ function normalizeOptions(moduleOptions) {
20
+ if (typeof moduleOptions === 'object' && moduleOptions) {
21
+ return {
22
+ removeWithAttributes: moduleOptions.removeWithAttributes === true
23
+ };
24
+ }
25
+ return {
26
+ removeWithAttributes: false
27
+ };
28
+ }
29
+ function hasAttributes(node) {
30
+ return !!node.attrs && Object.keys(node.attrs).length > 0;
31
+ }
32
+ function isIgnorableText(text) {
33
+ return isComment(text) || text.trim() === '';
34
+ }
35
+ function isEmptyContent(content) {
36
+ if (!content) {
37
+ return true;
38
+ }
39
+ const contentArray = Array.isArray(content) ? content : [
40
+ content
41
+ ];
42
+ if (contentArray.length === 0) {
43
+ return true;
44
+ }
45
+ return contentArray.every((child)=>typeof child === 'string' && isIgnorableText(child));
46
+ }
47
+ function shouldRemoveNode(node, options) {
48
+ if (!node.tag || typeof node.tag !== 'string') {
49
+ return false;
50
+ }
51
+ if (voidElements.has(node.tag.toLowerCase())) {
52
+ return false;
53
+ }
54
+ if (!options.removeWithAttributes && hasAttributes(node)) {
55
+ return false;
56
+ }
57
+ return isEmptyContent(node.content);
58
+ }
59
+ function pruneNodes(nodes, options) {
60
+ const result = [];
61
+ for (const node of nodes){
62
+ if (typeof node === 'string') {
63
+ result.push(node);
64
+ continue;
65
+ }
66
+ if (node.content) {
67
+ const contentArray = Array.isArray(node.content) ? node.content : [
68
+ node.content
69
+ ];
70
+ node.content = pruneNodes(contentArray, options);
71
+ }
72
+ if (shouldRemoveNode(node, options)) {
73
+ continue;
74
+ }
75
+ result.push(node);
76
+ }
77
+ return result;
78
+ }
79
+ const mod = {
80
+ default (tree, _options, moduleOptions) {
81
+ const normalizedOptions = normalizeOptions(moduleOptions);
82
+ const pruned = pruneNodes(tree, normalizedOptions);
83
+ tree.splice(0, tree.length, ...pruned);
84
+ return tree;
85
+ }
86
+ };
87
+
88
+ export { mod as default };