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,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,6 +2,60 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var helpers_js = require('../helpers.js');
4
4
 
5
+ const booleanAttrs = new Set([
6
+ 'amp-custom',
7
+ 'disabled'
8
+ ]);
9
+ function normalizeStyleType(attrs) {
10
+ if (!attrs || typeof attrs.type !== 'string') {
11
+ return 'text/css';
12
+ }
13
+ const type = attrs.type.trim();
14
+ return type ? type.toLowerCase() : 'text/css';
15
+ }
16
+ function normalizeStyleMedia(attrs) {
17
+ if (!attrs || typeof attrs.media !== 'string') {
18
+ return 'all';
19
+ }
20
+ const media = attrs.media.trim();
21
+ return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
22
+ }
23
+ function normalizeStyleAttrsForKey(attrs) {
24
+ const normalized = {};
25
+ for (const [key, value] of Object.entries(attrs || {})){
26
+ if (key === 'type' || key === 'media') {
27
+ continue;
28
+ }
29
+ if (value === undefined) {
30
+ continue;
31
+ }
32
+ if (booleanAttrs.has(key)) {
33
+ normalized[key] = true;
34
+ continue;
35
+ }
36
+ normalized[key] = value;
37
+ }
38
+ return normalized;
39
+ }
40
+ function buildStyleKey(attrs) {
41
+ const keyObject = {
42
+ type: normalizeStyleType(attrs),
43
+ media: normalizeStyleMedia(attrs),
44
+ ...normalizeStyleAttrsForKey(attrs)
45
+ };
46
+ const sortedKeys = Object.keys(keyObject).sort();
47
+ const sortedKeyObject = {};
48
+ for (const key of sortedKeys){
49
+ sortedKeyObject[key] = keyObject[key];
50
+ }
51
+ return JSON.stringify(sortedKeyObject);
52
+ }
53
+ function extractStyleTextContent(node) {
54
+ if (typeof node.content === 'string') {
55
+ return node.content;
56
+ }
57
+ return helpers_js.extractTextContentFromNode(node);
58
+ }
5
59
  /* Merge multiple <style> into one */ const mod = {
6
60
  default (tree) {
7
61
  const styleNodes = {};
@@ -20,12 +74,10 @@ var helpers_js = require('../helpers.js');
20
74
  if (helpers_js.isAmpBoilerplate(node)) {
21
75
  return node;
22
76
  }
23
- const styleType = nodeAttrs.type || 'text/css';
24
- const styleMedia = nodeAttrs.media || 'all';
25
- const styleKey = styleType + '_' + styleMedia;
77
+ const styleKey = buildStyleKey(nodeAttrs);
26
78
  if (styleKey in styleNodes) {
27
79
  var _styleNodes_styleKey, _content;
28
- const styleContent = helpers_js.extractTextContentFromNode(node);
80
+ const styleContent = extractStyleTextContent(node);
29
81
  (_content = (_styleNodes_styleKey = styleNodes[styleKey]).content) != null ? _content : _styleNodes_styleKey.content = [];
30
82
  styleNodes[styleKey].content.push(' ' + styleContent);
31
83
  return ''; // Remove node
@@ -1,5 +1,59 @@
1
1
  import { isAmpBoilerplate, extractTextContentFromNode } from '../helpers.mjs';
2
2
 
3
+ const booleanAttrs = new Set([
4
+ 'amp-custom',
5
+ 'disabled'
6
+ ]);
7
+ function normalizeStyleType(attrs) {
8
+ if (!attrs || typeof attrs.type !== 'string') {
9
+ return 'text/css';
10
+ }
11
+ const type = attrs.type.trim();
12
+ return type ? type.toLowerCase() : 'text/css';
13
+ }
14
+ function normalizeStyleMedia(attrs) {
15
+ if (!attrs || typeof attrs.media !== 'string') {
16
+ return 'all';
17
+ }
18
+ const media = attrs.media.trim();
19
+ return media ? media.replace(/\s+/g, ' ').toLowerCase() : 'all';
20
+ }
21
+ function normalizeStyleAttrsForKey(attrs) {
22
+ const normalized = {};
23
+ for (const [key, value] of Object.entries(attrs || {})){
24
+ if (key === 'type' || key === 'media') {
25
+ continue;
26
+ }
27
+ if (value === undefined) {
28
+ continue;
29
+ }
30
+ if (booleanAttrs.has(key)) {
31
+ normalized[key] = true;
32
+ continue;
33
+ }
34
+ normalized[key] = value;
35
+ }
36
+ return normalized;
37
+ }
38
+ function buildStyleKey(attrs) {
39
+ const keyObject = {
40
+ type: normalizeStyleType(attrs),
41
+ media: normalizeStyleMedia(attrs),
42
+ ...normalizeStyleAttrsForKey(attrs)
43
+ };
44
+ const sortedKeys = Object.keys(keyObject).sort();
45
+ const sortedKeyObject = {};
46
+ for (const key of sortedKeys){
47
+ sortedKeyObject[key] = keyObject[key];
48
+ }
49
+ return JSON.stringify(sortedKeyObject);
50
+ }
51
+ function extractStyleTextContent(node) {
52
+ if (typeof node.content === 'string') {
53
+ return node.content;
54
+ }
55
+ return extractTextContentFromNode(node);
56
+ }
3
57
  /* Merge multiple <style> into one */ const mod = {
4
58
  default (tree) {
5
59
  const styleNodes = {};
@@ -18,12 +72,10 @@ import { isAmpBoilerplate, extractTextContentFromNode } from '../helpers.mjs';
18
72
  if (isAmpBoilerplate(node)) {
19
73
  return node;
20
74
  }
21
- const styleType = nodeAttrs.type || 'text/css';
22
- const styleMedia = nodeAttrs.media || 'all';
23
- const styleKey = styleType + '_' + styleMedia;
75
+ const styleKey = buildStyleKey(nodeAttrs);
24
76
  if (styleKey in styleNodes) {
25
77
  var _styleNodes_styleKey, _content;
26
- const styleContent = extractTextContentFromNode(node);
78
+ const styleContent = extractStyleTextContent(node);
27
79
  (_content = (_styleNodes_styleKey = styleNodes[styleKey]).content) != null ? _content : _styleNodes_styleKey.content = [];
28
80
  styleNodes[styleKey].content.push(' ' + styleContent);
29
81
  return ''; // Remove node
@@ -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
+ type RedundantWhitespaceMode = 'safe' | 'agressive' | false;
89
+ type MinifyAttributesOptions = {
90
+ metaContent?: boolean;
91
+ redundantWhitespaces?: RedundantWhitespaceMode | 'aggressive';
92
+ };
93
+ declare const mod: HtmlnanoModule<MinifyAttributesOptions>;
94
+
95
+ export { mod as default };
@@ -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
+ type RedundantWhitespaceMode = 'safe' | 'agressive' | false;
89
+ type MinifyAttributesOptions = {
90
+ metaContent?: boolean;
91
+ redundantWhitespaces?: RedundantWhitespaceMode | 'aggressive';
92
+ };
93
+ declare const mod: HtmlnanoModule<MinifyAttributesOptions>;
94
+
95
+ export { mod as default };
@@ -0,0 +1,159 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ var helpers_js = require('../helpers.js');
4
+ var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
5
+
6
+ const asciiWhitespace = new Set([
7
+ '\t',
8
+ '\n',
9
+ '\f',
10
+ '\r',
11
+ ' '
12
+ ]);
13
+ const defaultOptions = {
14
+ metaContent: true,
15
+ redundantWhitespaces: 'safe'
16
+ };
17
+ function isAsciiWhitespace(char) {
18
+ return asciiWhitespace.has(char);
19
+ }
20
+ function isAsciiDigit(char) {
21
+ return char >= '0' && char <= '9';
22
+ }
23
+ function skipAsciiWhitespace(input, start) {
24
+ let pos = start;
25
+ while(pos < input.length && isAsciiWhitespace(input[pos])){
26
+ pos += 1;
27
+ }
28
+ return pos;
29
+ }
30
+ function minifyMetaRefreshValue(value) {
31
+ const input = value;
32
+ let pos = skipAsciiWhitespace(input, 0);
33
+ const timeStart = pos;
34
+ while(pos < input.length && isAsciiDigit(input[pos])){
35
+ pos += 1;
36
+ }
37
+ if (pos === timeStart) return null;
38
+ const time = input.slice(timeStart, pos);
39
+ while(pos < input.length){
40
+ const ch = input[pos];
41
+ if (isAsciiDigit(ch) || ch === '.') {
42
+ pos += 1;
43
+ continue;
44
+ }
45
+ break;
46
+ }
47
+ pos = skipAsciiWhitespace(input, pos);
48
+ if (pos >= input.length) return time;
49
+ const separator = input[pos];
50
+ if (separator !== ';' && separator !== ',') return null;
51
+ pos += 1;
52
+ pos = skipAsciiWhitespace(input, pos);
53
+ if (pos >= input.length) return time;
54
+ let hasUrlPrefix = false;
55
+ if (input[pos] === 'u' || input[pos] === 'U') {
56
+ if (pos + 2 < input.length) {
57
+ const maybeUrl = input.slice(pos, pos + 3);
58
+ if (maybeUrl.toLowerCase() === 'url') {
59
+ let prefixPos = skipAsciiWhitespace(input, pos + 3);
60
+ if (prefixPos < input.length && input[prefixPos] === '=') {
61
+ prefixPos = skipAsciiWhitespace(input, prefixPos + 1);
62
+ hasUrlPrefix = true;
63
+ pos = prefixPos;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ if (pos >= input.length) return time;
69
+ const firstUrlChar = input[pos];
70
+ if (firstUrlChar === '"' || firstUrlChar === '\'') {
71
+ if (!hasUrlPrefix) return null;
72
+ const quote = firstUrlChar;
73
+ const urlStart = pos + 1;
74
+ const quoteIndex = input.indexOf(quote, urlStart);
75
+ const url = quoteIndex === -1 ? input.slice(urlStart) : input.slice(urlStart, quoteIndex);
76
+ if (!url) return time;
77
+ const closingQuote = quoteIndex === -1 ? '' : quote;
78
+ return `${time}${separator} URL=${quote}${url}${closingQuote}`;
79
+ }
80
+ const url = input.slice(pos).trim();
81
+ if (!url) return time;
82
+ return `${time}${separator} ${url}`;
83
+ }
84
+ function isMetaRefresh(attrs, tagName) {
85
+ if (!tagName || tagName.toLowerCase() !== 'meta') return false;
86
+ const httpEquiv = attrs['http-equiv'];
87
+ if (typeof httpEquiv !== 'string') return false;
88
+ return httpEquiv.trim().toLowerCase() === 'refresh';
89
+ }
90
+ function normalizeOptions(moduleOptions) {
91
+ if (moduleOptions && typeof moduleOptions === 'object') {
92
+ let redundantWhitespaces = defaultOptions.redundantWhitespaces;
93
+ if (moduleOptions.redundantWhitespaces === 'aggressive') {
94
+ redundantWhitespaces = 'agressive';
95
+ } else if (moduleOptions.redundantWhitespaces === 'safe' || moduleOptions.redundantWhitespaces === 'agressive' || moduleOptions.redundantWhitespaces === false) {
96
+ redundantWhitespaces = moduleOptions.redundantWhitespaces;
97
+ }
98
+ return {
99
+ metaContent: moduleOptions.metaContent !== false,
100
+ redundantWhitespaces
101
+ };
102
+ }
103
+ return defaultOptions;
104
+ }
105
+ function collapseWhitespace(value) {
106
+ return value.replace(/\s+/g, ' ').trim();
107
+ }
108
+ function minifyAttributeWhitespace(mode, attrName, attrValue, tagName) {
109
+ if (!mode) {
110
+ return null;
111
+ }
112
+ const attrNameLower = attrName.toLowerCase();
113
+ if (collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
114
+ const collapsed = collapseWhitespace(attrValue);
115
+ return collapsed === attrValue ? null : collapsed;
116
+ }
117
+ if (helpers_js.isEventHandler(attrName)) {
118
+ const trimmed = attrValue.trim();
119
+ return trimmed === attrValue ? null : trimmed;
120
+ }
121
+ if (collapseAttributeWhitespace_js.isSingleValueAttribute(attrNameLower, tagName)) {
122
+ const trimmed = attrValue.trim();
123
+ return trimmed === attrValue ? null : trimmed;
124
+ }
125
+ if (mode === 'agressive') {
126
+ const trimmed = attrValue.trim();
127
+ return trimmed === attrValue ? null : trimmed;
128
+ }
129
+ return null;
130
+ }
131
+ const mod = {
132
+ onAttrs (_options, moduleOptions) {
133
+ const normalizedOptions = normalizeOptions(moduleOptions);
134
+ return (attrs, node)=>{
135
+ if (normalizedOptions.metaContent && isMetaRefresh(attrs, node.tag)) {
136
+ const content = attrs.content;
137
+ if (typeof content === 'string') {
138
+ const minified = minifyMetaRefreshValue(content);
139
+ if (minified !== null && minified !== content) {
140
+ attrs.content = minified;
141
+ }
142
+ }
143
+ }
144
+ if (normalizedOptions.redundantWhitespaces) {
145
+ const tagName = node.tag ? node.tag.toLowerCase() : undefined;
146
+ Object.entries(attrs).forEach(([attrName, attrValue])=>{
147
+ if (typeof attrValue !== 'string') return;
148
+ const minified = minifyAttributeWhitespace(normalizedOptions.redundantWhitespaces, attrName, attrValue, tagName);
149
+ if (minified !== null) {
150
+ attrs[attrName] = minified;
151
+ }
152
+ });
153
+ }
154
+ return attrs;
155
+ };
156
+ }
157
+ };
158
+
159
+ exports.default = mod;