htmlnano 3.1.0 → 3.2.1

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 (88) hide show
  1. package/README.md +37 -23
  2. package/dist/_modules/collapseAttributeWhitespace.d.mts +58 -14
  3. package/dist/_modules/collapseAttributeWhitespace.d.ts +58 -14
  4. package/dist/_modules/collapseBooleanAttributes.d.mts +58 -14
  5. package/dist/_modules/collapseBooleanAttributes.d.ts +58 -14
  6. package/dist/_modules/collapseWhitespace.d.mts +58 -14
  7. package/dist/_modules/collapseWhitespace.d.ts +58 -14
  8. package/dist/_modules/custom.d.mts +58 -14
  9. package/dist/_modules/custom.d.ts +58 -14
  10. package/dist/_modules/deduplicateAttributeValues.d.mts +58 -14
  11. package/dist/_modules/deduplicateAttributeValues.d.ts +58 -14
  12. package/dist/_modules/example.d.mts +58 -14
  13. package/dist/_modules/example.d.ts +58 -14
  14. package/dist/_modules/mergeScripts.d.mts +58 -14
  15. package/dist/_modules/mergeScripts.d.ts +58 -14
  16. package/dist/_modules/mergeScripts.js +30 -23
  17. package/dist/_modules/mergeScripts.mjs +30 -23
  18. package/dist/_modules/mergeStyles.d.mts +58 -14
  19. package/dist/_modules/mergeStyles.d.ts +58 -14
  20. package/dist/_modules/mergeStyles.js +26 -21
  21. package/dist/_modules/mergeStyles.mjs +26 -21
  22. package/dist/_modules/minifyAttributes.d.mts +58 -14
  23. package/dist/_modules/minifyAttributes.d.ts +58 -14
  24. package/dist/_modules/minifyAttributes.js +2 -2
  25. package/dist/_modules/minifyAttributes.mjs +2 -2
  26. package/dist/_modules/minifyConditionalComments.d.mts +58 -14
  27. package/dist/_modules/minifyConditionalComments.d.ts +58 -14
  28. package/dist/_modules/minifyCss.d.mts +61 -14
  29. package/dist/_modules/minifyCss.d.ts +61 -14
  30. package/dist/_modules/minifyCss.js +79 -12
  31. package/dist/_modules/minifyCss.mjs +79 -13
  32. package/dist/_modules/minifyHtmlTemplate.d.mts +58 -14
  33. package/dist/_modules/minifyHtmlTemplate.d.ts +58 -14
  34. package/dist/_modules/minifyJs.d.mts +58 -13
  35. package/dist/_modules/minifyJs.d.ts +58 -13
  36. package/dist/_modules/minifyJs.js +12 -0
  37. package/dist/_modules/minifyJs.mjs +12 -0
  38. package/dist/_modules/minifyJson.d.mts +58 -14
  39. package/dist/_modules/minifyJson.d.ts +58 -14
  40. package/dist/_modules/minifyJson.js +1 -1
  41. package/dist/_modules/minifyJson.mjs +1 -1
  42. package/dist/_modules/minifySvg.d.mts +58 -13
  43. package/dist/_modules/minifySvg.d.ts +58 -13
  44. package/dist/_modules/minifySvg.js +2 -2
  45. package/dist/_modules/minifySvg.mjs +2 -2
  46. package/dist/_modules/minifyUrls.d.mts +58 -14
  47. package/dist/_modules/minifyUrls.d.ts +58 -14
  48. package/dist/_modules/minifyUrls.js +2 -2
  49. package/dist/_modules/minifyUrls.mjs +2 -2
  50. package/dist/_modules/normalizeAttributeValues.d.mts +58 -14
  51. package/dist/_modules/normalizeAttributeValues.d.ts +58 -14
  52. package/dist/_modules/removeAttributeQuotes.d.mts +58 -14
  53. package/dist/_modules/removeAttributeQuotes.d.ts +58 -14
  54. package/dist/_modules/removeComments.d.mts +58 -14
  55. package/dist/_modules/removeComments.d.ts +58 -14
  56. package/dist/_modules/removeComments.js +1 -1
  57. package/dist/_modules/removeComments.mjs +1 -1
  58. package/dist/_modules/removeEmptyAttributes.d.mts +58 -14
  59. package/dist/_modules/removeEmptyAttributes.d.ts +58 -14
  60. package/dist/_modules/removeEmptyElements.d.mts +58 -14
  61. package/dist/_modules/removeEmptyElements.d.ts +58 -14
  62. package/dist/_modules/removeOptionalTags.d.mts +58 -14
  63. package/dist/_modules/removeOptionalTags.d.ts +58 -14
  64. package/dist/_modules/removeRedundantAttributes.d.mts +58 -14
  65. package/dist/_modules/removeRedundantAttributes.d.ts +58 -14
  66. package/dist/_modules/removeUnusedCss.d.mts +58 -14
  67. package/dist/_modules/removeUnusedCss.d.ts +58 -14
  68. package/dist/_modules/removeUnusedCss.js +3 -2
  69. package/dist/_modules/removeUnusedCss.mjs +3 -2
  70. package/dist/_modules/sortAttributes.d.mts +61 -16
  71. package/dist/_modules/sortAttributes.d.ts +61 -16
  72. package/dist/_modules/sortAttributes.js +5 -9
  73. package/dist/_modules/sortAttributes.mjs +5 -9
  74. package/dist/_modules/sortAttributesWithLists.d.mts +61 -15
  75. package/dist/_modules/sortAttributesWithLists.d.ts +61 -15
  76. package/dist/_modules/sortAttributesWithLists.js +43 -56
  77. package/dist/_modules/sortAttributesWithLists.mjs +43 -56
  78. package/dist/index.d.ts +59 -15
  79. package/dist/index.js +7 -6
  80. package/dist/index.mjs +7 -6
  81. package/dist/presets/ampSafe.d.ts +57 -12
  82. package/dist/presets/max.d.ts +57 -12
  83. package/dist/presets/max.js +9 -5
  84. package/dist/presets/max.mjs +9 -5
  85. package/dist/presets/safe.d.ts +57 -12
  86. package/dist/presets/safe.js +1 -1
  87. package/dist/presets/safe.mjs +1 -1
  88. package/package.json +20 -14
@@ -1,25 +1,70 @@
1
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
2
 
7
- type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
3
+ type PostHTMLNodeLike = PostHTML.Node | string;
4
+ type PostHTMLTreeLike = [PostHTMLNodeLike] & PostHTML.NodeAPI & {
8
5
  options?: {
9
6
  quoteAllAttributes?: boolean | undefined;
10
7
  quoteStyle?: 0 | 1 | 2 | undefined;
11
8
  replaceQuote?: boolean | undefined;
12
9
  } | undefined;
13
10
  render(): string;
14
- render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
11
+ render(node: PostHTMLNodeLike | PostHTMLTreeLike, renderOptions?: any): string;
15
12
  };
16
13
  type MaybeArray<T> = T | Array<T>;
17
- type PostHTMLNodeLike = PostHTML.Node | string;
18
14
  type HtmlnanoTemplateRule = {
19
15
  tag: string;
20
16
  attrs?: Record<string, string | boolean | void>;
21
17
  };
22
18
  type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
19
+ type HtmlnanoMinifyCssOptions = object;
20
+ type HtmlnanoMinifyJsOptions = object;
21
+ type HtmlnanoMinifySvgOptions = object;
22
+ type HtmlnanoPurgeCssPattern = string | RegExp;
23
+ type HtmlnanoPurgeCssExtractorResultDetailed = {
24
+ attributes: {
25
+ names: string[];
26
+ values: string[];
27
+ };
28
+ classes: string[];
29
+ ids: string[];
30
+ tags: string[];
31
+ undetermined: string[];
32
+ };
33
+ type HtmlnanoPurgeCssExtractorResult = HtmlnanoPurgeCssExtractorResultDetailed | string[];
34
+ type HtmlnanoPurgeCssDefaultExtractor = (content: string) => HtmlnanoPurgeCssExtractorResult;
35
+ type HtmlnanoPurgeCssSourceMapOptions = {
36
+ absolute?: boolean;
37
+ annotation?: boolean | string;
38
+ from?: string;
39
+ inline?: boolean;
40
+ prev?: boolean | object | string;
41
+ sourcesContent?: boolean;
42
+ to?: string;
43
+ };
44
+ type HtmlnanoPurgeCssSafelist = HtmlnanoPurgeCssPattern[] | {
45
+ standard?: HtmlnanoPurgeCssPattern[];
46
+ deep?: RegExp[];
47
+ greedy?: RegExp[];
48
+ variables?: HtmlnanoPurgeCssPattern[];
49
+ keyframes?: HtmlnanoPurgeCssPattern[];
50
+ };
51
+ interface HtmlnanoPurgeCssOptions {
52
+ tool: 'purgeCSS';
53
+ defaultExtractor?: HtmlnanoPurgeCssDefaultExtractor;
54
+ fontFace?: boolean;
55
+ keyframes?: boolean;
56
+ output?: string;
57
+ rejected?: boolean;
58
+ rejectedCss?: boolean;
59
+ sourceMap?: boolean | HtmlnanoPurgeCssSourceMapOptions;
60
+ stdin?: boolean;
61
+ stdout?: boolean;
62
+ variables?: boolean;
63
+ safelist?: HtmlnanoPurgeCssSafelist;
64
+ blocklist?: HtmlnanoPurgeCssPattern[];
65
+ skippedContentGlobs?: string[];
66
+ dynamicAttributes?: string[];
67
+ }
23
68
  interface HtmlnanoOptions {
24
69
  skipConfigLoading?: boolean;
25
70
  configPath?: string;
@@ -34,16 +79,16 @@ interface HtmlnanoOptions {
34
79
  minifyUrls?: URL | string | false;
35
80
  mergeStyles?: boolean;
36
81
  mergeScripts?: boolean;
37
- minifyCss?: Options | boolean;
82
+ minifyCss?: HtmlnanoMinifyCssOptions | boolean;
38
83
  minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
84
  minifyConditionalComments?: boolean;
40
- minifyJs?: MinifyOptions | boolean;
85
+ minifyJs?: HtmlnanoMinifyJsOptions | boolean;
41
86
  minifyJson?: boolean;
42
87
  minifyAttributes?: boolean | {
43
88
  metaContent?: boolean;
44
89
  redundantWhitespaces?: 'safe' | 'agressive' | false;
45
90
  };
46
- minifySvg?: Config | boolean;
91
+ minifySvg?: HtmlnanoMinifySvgOptions | boolean;
47
92
  normalizeAttributeValues?: boolean;
48
93
  removeAttributeQuotes?: boolean | {
49
94
  force?: boolean;
@@ -55,9 +100,8 @@ interface HtmlnanoOptions {
55
100
  };
56
101
  removeRedundantAttributes?: boolean;
57
102
  removeOptionalTags?: boolean;
58
- removeUnusedCss?: boolean | ({
59
- tool: 'purgeCSS';
60
- } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
103
+ removeUnusedCss?: boolean | HtmlnanoPurgeCssOptions | {
104
+ tool?: 'uncss';
61
105
  banner?: boolean;
62
106
  csspath?: string;
63
107
  htmlroot?: string;
@@ -77,7 +121,7 @@ interface HtmlnanoOptions {
77
121
  type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
122
  type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
123
  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;
124
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends Array<infer Item> ? Array<Item> : T extends object ? Partial<T> : T;
81
125
  type HtmlnanoModule<Options = any> = {
82
126
  onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
127
  onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
@@ -85,6 +129,8 @@ type HtmlnanoModule<Options = any> = {
85
129
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
86
130
  };
87
131
 
88
- declare const mod: HtmlnanoModule<boolean | 'alphabetical' | 'frequency'>;
132
+ type SortAttributesOption = 'alphabetical' | 'frequency';
133
+
134
+ declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
89
135
 
90
136
  export { mod as default };
@@ -1,25 +1,70 @@
1
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
2
 
7
- type PostHTMLTreeLike = [PostHTML.Node] & PostHTML.NodeAPI & {
3
+ type PostHTMLNodeLike = PostHTML.Node | string;
4
+ type PostHTMLTreeLike = [PostHTMLNodeLike] & PostHTML.NodeAPI & {
8
5
  options?: {
9
6
  quoteAllAttributes?: boolean | undefined;
10
7
  quoteStyle?: 0 | 1 | 2 | undefined;
11
8
  replaceQuote?: boolean | undefined;
12
9
  } | undefined;
13
10
  render(): string;
14
- render(node: PostHTML.Node | PostHTMLTreeLike, renderOptions?: any): string;
11
+ render(node: PostHTMLNodeLike | PostHTMLTreeLike, renderOptions?: any): string;
15
12
  };
16
13
  type MaybeArray<T> = T | Array<T>;
17
- type PostHTMLNodeLike = PostHTML.Node | string;
18
14
  type HtmlnanoTemplateRule = {
19
15
  tag: string;
20
16
  attrs?: Record<string, string | boolean | void>;
21
17
  };
22
18
  type MinifyHtmlTemplateOptions = boolean | HtmlnanoTemplateRule[];
19
+ type HtmlnanoMinifyCssOptions = object;
20
+ type HtmlnanoMinifyJsOptions = object;
21
+ type HtmlnanoMinifySvgOptions = object;
22
+ type HtmlnanoPurgeCssPattern = string | RegExp;
23
+ type HtmlnanoPurgeCssExtractorResultDetailed = {
24
+ attributes: {
25
+ names: string[];
26
+ values: string[];
27
+ };
28
+ classes: string[];
29
+ ids: string[];
30
+ tags: string[];
31
+ undetermined: string[];
32
+ };
33
+ type HtmlnanoPurgeCssExtractorResult = HtmlnanoPurgeCssExtractorResultDetailed | string[];
34
+ type HtmlnanoPurgeCssDefaultExtractor = (content: string) => HtmlnanoPurgeCssExtractorResult;
35
+ type HtmlnanoPurgeCssSourceMapOptions = {
36
+ absolute?: boolean;
37
+ annotation?: boolean | string;
38
+ from?: string;
39
+ inline?: boolean;
40
+ prev?: boolean | object | string;
41
+ sourcesContent?: boolean;
42
+ to?: string;
43
+ };
44
+ type HtmlnanoPurgeCssSafelist = HtmlnanoPurgeCssPattern[] | {
45
+ standard?: HtmlnanoPurgeCssPattern[];
46
+ deep?: RegExp[];
47
+ greedy?: RegExp[];
48
+ variables?: HtmlnanoPurgeCssPattern[];
49
+ keyframes?: HtmlnanoPurgeCssPattern[];
50
+ };
51
+ interface HtmlnanoPurgeCssOptions {
52
+ tool: 'purgeCSS';
53
+ defaultExtractor?: HtmlnanoPurgeCssDefaultExtractor;
54
+ fontFace?: boolean;
55
+ keyframes?: boolean;
56
+ output?: string;
57
+ rejected?: boolean;
58
+ rejectedCss?: boolean;
59
+ sourceMap?: boolean | HtmlnanoPurgeCssSourceMapOptions;
60
+ stdin?: boolean;
61
+ stdout?: boolean;
62
+ variables?: boolean;
63
+ safelist?: HtmlnanoPurgeCssSafelist;
64
+ blocklist?: HtmlnanoPurgeCssPattern[];
65
+ skippedContentGlobs?: string[];
66
+ dynamicAttributes?: string[];
67
+ }
23
68
  interface HtmlnanoOptions {
24
69
  skipConfigLoading?: boolean;
25
70
  configPath?: string;
@@ -34,16 +79,16 @@ interface HtmlnanoOptions {
34
79
  minifyUrls?: URL | string | false;
35
80
  mergeStyles?: boolean;
36
81
  mergeScripts?: boolean;
37
- minifyCss?: Options | boolean;
82
+ minifyCss?: HtmlnanoMinifyCssOptions | boolean;
38
83
  minifyHtmlTemplate?: MinifyHtmlTemplateOptions;
39
84
  minifyConditionalComments?: boolean;
40
- minifyJs?: MinifyOptions | boolean;
85
+ minifyJs?: HtmlnanoMinifyJsOptions | boolean;
41
86
  minifyJson?: boolean;
42
87
  minifyAttributes?: boolean | {
43
88
  metaContent?: boolean;
44
89
  redundantWhitespaces?: 'safe' | 'agressive' | false;
45
90
  };
46
- minifySvg?: Config | boolean;
91
+ minifySvg?: HtmlnanoMinifySvgOptions | boolean;
47
92
  normalizeAttributeValues?: boolean;
48
93
  removeAttributeQuotes?: boolean | {
49
94
  force?: boolean;
@@ -55,9 +100,8 @@ interface HtmlnanoOptions {
55
100
  };
56
101
  removeRedundantAttributes?: boolean;
57
102
  removeOptionalTags?: boolean;
58
- removeUnusedCss?: boolean | ({
59
- tool: 'purgeCSS';
60
- } & Omit<UserDefinedOptions, 'content' | 'css' | 'extractors'>) | {
103
+ removeUnusedCss?: boolean | HtmlnanoPurgeCssOptions | {
104
+ tool?: 'uncss';
61
105
  banner?: boolean;
62
106
  csspath?: string;
63
107
  htmlroot?: string;
@@ -77,7 +121,7 @@ interface HtmlnanoOptions {
77
121
  type HtmlnanoModuleAttrsHandler = (attrs: Record<string, string | boolean | void>, node: PostHTML.Node) => Record<string, string | boolean | void>;
78
122
  type HtmlnanoModuleContentHandler = (content: Array<PostHTMLNodeLike>, node: PostHTML.Node) => MaybeArray<PostHTMLNodeLike>;
79
123
  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;
124
+ type OptionalOptions<T> = T extends boolean | string | Function | number | null | undefined ? T : T extends Array<infer Item> ? Array<Item> : T extends object ? Partial<T> : T;
81
125
  type HtmlnanoModule<Options = any> = {
82
126
  onAttrs?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleAttrsHandler;
83
127
  onContent?: (options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => HtmlnanoModuleContentHandler;
@@ -85,6 +129,8 @@ type HtmlnanoModule<Options = any> = {
85
129
  default?: (tree: PostHTMLTreeLike, options: Partial<HtmlnanoOptions>, moduleOptions: OptionalOptions<Options>) => PostHTMLTreeLike | Promise<PostHTMLTreeLike>;
86
130
  };
87
131
 
88
- declare const mod: HtmlnanoModule<boolean | 'alphabetical' | 'frequency'>;
132
+ type SortAttributesOption = 'alphabetical' | 'frequency';
133
+
134
+ declare const mod: HtmlnanoModule<boolean | SortAttributesOption>;
89
135
 
90
136
  export { mod as default };
@@ -2,35 +2,36 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var collapseAttributeWhitespace_js = require('./collapseAttributeWhitespace.js');
4
4
 
5
- // class, rel, ping
6
5
  const validOptions = new Set([
7
6
  'frequency',
8
7
  'alphabetical'
9
8
  ]);
10
- const processModuleOptions = (options)=>{
9
+ function resolveSortType(options) {
11
10
  if (options === true) return 'alphabetical';
12
11
  if (options === false) return false;
13
12
  return validOptions.has(options) ? options : false;
14
- };
15
- class AttributeTokenChain {
13
+ }
14
+
15
+ // class, rel, ping
16
+ class ListAttributeTokenChain {
16
17
  addFromNodeAttrsArray(attrValuesArray) {
17
18
  attrValuesArray.forEach((attrValue)=>{
18
19
  if (!attrValue) {
19
20
  return;
20
21
  }
21
- if (this.freqData.has(attrValue)) {
22
- this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
22
+ if (this.tokenCounts.has(attrValue)) {
23
+ this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
23
24
  } else {
24
- this.freqData.set(attrValue, 1);
25
+ this.tokenCounts.set(attrValue, 1);
25
26
  }
26
27
  });
27
28
  }
28
29
  createSortOrder() {
29
- const _sortOrder = [
30
- ...this.freqData.entries()
30
+ const nextSortOrder = [
31
+ ...this.tokenCounts.entries()
31
32
  ];
32
- _sortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
33
- this.sortOrder = _sortOrder.map((i)=>i[0]);
33
+ nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
34
+ this.sortedTokens = nextSortOrder.map((i)=>i[0]);
34
35
  }
35
36
  sortFromNodeAttrsArray(attrValuesArray) {
36
37
  const resultArray = [];
@@ -42,10 +43,10 @@ class AttributeTokenChain {
42
43
  }
43
44
  tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
44
45
  });
45
- if (!this.sortOrder) {
46
+ if (!this.sortedTokens) {
46
47
  this.createSortOrder();
47
48
  }
48
- this.sortOrder.forEach((k)=>{
49
+ this.sortedTokens.forEach((k)=>{
49
50
  const count = tokenCounts.get(k);
50
51
  if (!count) {
51
52
  return;
@@ -57,13 +58,13 @@ class AttributeTokenChain {
57
58
  return resultArray;
58
59
  }
59
60
  constructor(){
60
- /** <attr, frequency> */ this.freqData = new Map();
61
- this.sortOrder = null;
61
+ /** <attr, frequency> */ this.tokenCounts = new Map();
62
+ this.sortedTokens = null;
62
63
  }
63
64
  }
64
65
  /** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
65
66
  default (tree, options, moduleOptions) {
66
- const sortType = processModuleOptions(moduleOptions);
67
+ const sortType = resolveSortType(moduleOptions);
67
68
  if (sortType === 'alphabetical') {
68
69
  return sortAttributesWithListsInAlphabeticalOrder(tree);
69
70
  }
@@ -75,64 +76,50 @@ class AttributeTokenChain {
75
76
  }
76
77
  };
77
78
  const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
78
- function sortAttributesWithListsInAlphabeticalOrder(tree) {
79
+ function walkListAttributes(tree, walkFn) {
79
80
  tree.walk((node)=>{
80
81
  if (!node.attrs) {
81
82
  return node;
82
83
  }
83
84
  const tagName = node.tag ? node.tag.toLowerCase() : undefined;
84
- Object.keys(node.attrs).forEach((attrName)=>{
85
+ Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
85
86
  const attrNameLower = attrName.toLowerCase();
86
- if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
87
- return;
88
- }
89
- const attrValues = splitListAttributeValues(node.attrs[attrName]);
90
- if (attrValues.length < 2) {
87
+ if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
91
88
  return;
92
89
  }
93
- node.attrs[attrName] = attrValues.sort((a, b)=>{
94
- // @ts-expect-error -- deliberately use minus operator to sort things
95
- return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
96
- }).join(' ');
90
+ walkFn(node.attrs, attrName, attrValues);
97
91
  });
98
92
  return node;
99
93
  });
94
+ }
95
+ function sortAttributesWithListsInAlphabeticalOrder(tree) {
96
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
97
+ const values = splitListAttributeValues(attrValues);
98
+ if (values.length < 2) {
99
+ return;
100
+ }
101
+ nodeAttrs[attrName] = values.sort((a, b)=>{
102
+ // @ts-expect-error -- deliberately use minus operator to sort things
103
+ return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
104
+ }).join(' ');
105
+ });
100
106
  return tree;
101
107
  }
102
108
  function sortAttributesWithListsByFrequency(tree) {
103
- const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
109
+ const tokenChainObj = {};
104
110
  // Traverse through tree to get frequency
105
- tree.walk((node)=>{
106
- if (!node.attrs) {
107
- return node;
108
- }
109
- const tagName = node.tag ? node.tag.toLowerCase() : undefined;
110
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
111
- const attrNameLower = attrName.toLowerCase();
112
- if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
113
- return;
114
- }
115
- tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
116
- tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
117
- });
118
- return node;
111
+ walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
112
+ const attrNameLower = attrName.toLowerCase();
113
+ tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
114
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
119
115
  });
120
116
  // Traverse through tree again, this time sort the attribute values
121
- tree.walk((node)=>{
122
- if (!node.attrs) {
123
- return node;
117
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
118
+ const attrNameLower = attrName.toLowerCase();
119
+ if (!tokenChainObj[attrNameLower]) {
120
+ return;
124
121
  }
125
- const tagName = node.tag ? node.tag.toLowerCase() : undefined;
126
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
127
- const attrNameLower = attrName.toLowerCase();
128
- if (!collapseAttributeWhitespace_js.isListAttribute(attrNameLower, tagName)) {
129
- return;
130
- }
131
- if (tokenChainObj[attrNameLower]) {
132
- node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
133
- }
134
- });
135
- return node;
122
+ nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
136
123
  });
137
124
  return tree;
138
125
  }
@@ -1,34 +1,35 @@
1
1
  import { isListAttribute } from './collapseAttributeWhitespace.mjs';
2
2
 
3
- // class, rel, ping
4
3
  const validOptions = new Set([
5
4
  'frequency',
6
5
  'alphabetical'
7
6
  ]);
8
- const processModuleOptions = (options)=>{
7
+ function resolveSortType(options) {
9
8
  if (options === true) return 'alphabetical';
10
9
  if (options === false) return false;
11
10
  return validOptions.has(options) ? options : false;
12
- };
13
- class AttributeTokenChain {
11
+ }
12
+
13
+ // class, rel, ping
14
+ class ListAttributeTokenChain {
14
15
  addFromNodeAttrsArray(attrValuesArray) {
15
16
  attrValuesArray.forEach((attrValue)=>{
16
17
  if (!attrValue) {
17
18
  return;
18
19
  }
19
- if (this.freqData.has(attrValue)) {
20
- this.freqData.set(attrValue, this.freqData.get(attrValue) + 1);
20
+ if (this.tokenCounts.has(attrValue)) {
21
+ this.tokenCounts.set(attrValue, this.tokenCounts.get(attrValue) + 1);
21
22
  } else {
22
- this.freqData.set(attrValue, 1);
23
+ this.tokenCounts.set(attrValue, 1);
23
24
  }
24
25
  });
25
26
  }
26
27
  createSortOrder() {
27
- const _sortOrder = [
28
- ...this.freqData.entries()
28
+ const nextSortOrder = [
29
+ ...this.tokenCounts.entries()
29
30
  ];
30
- _sortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
31
- this.sortOrder = _sortOrder.map((i)=>i[0]);
31
+ nextSortOrder.sort((a, b)=>b[1] - a[1] || a[0].localeCompare(b[0]));
32
+ this.sortedTokens = nextSortOrder.map((i)=>i[0]);
32
33
  }
33
34
  sortFromNodeAttrsArray(attrValuesArray) {
34
35
  const resultArray = [];
@@ -40,10 +41,10 @@ class AttributeTokenChain {
40
41
  }
41
42
  tokenCounts.set(attrValue, ((_tokenCounts_get = tokenCounts.get(attrValue)) != null ? _tokenCounts_get : 0) + 1);
42
43
  });
43
- if (!this.sortOrder) {
44
+ if (!this.sortedTokens) {
44
45
  this.createSortOrder();
45
46
  }
46
- this.sortOrder.forEach((k)=>{
47
+ this.sortedTokens.forEach((k)=>{
47
48
  const count = tokenCounts.get(k);
48
49
  if (!count) {
49
50
  return;
@@ -55,13 +56,13 @@ class AttributeTokenChain {
55
56
  return resultArray;
56
57
  }
57
58
  constructor(){
58
- /** <attr, frequency> */ this.freqData = new Map();
59
- this.sortOrder = null;
59
+ /** <attr, frequency> */ this.tokenCounts = new Map();
60
+ this.sortedTokens = null;
60
61
  }
61
62
  }
62
63
  /** Sort values inside list-like attributes (e.g. class, rel) */ const mod = {
63
64
  default (tree, options, moduleOptions) {
64
- const sortType = processModuleOptions(moduleOptions);
65
+ const sortType = resolveSortType(moduleOptions);
65
66
  if (sortType === 'alphabetical') {
66
67
  return sortAttributesWithListsInAlphabeticalOrder(tree);
67
68
  }
@@ -73,64 +74,50 @@ class AttributeTokenChain {
73
74
  }
74
75
  };
75
76
  const splitListAttributeValues = (attrValue)=>attrValue.split(/\s+/).filter(Boolean);
76
- function sortAttributesWithListsInAlphabeticalOrder(tree) {
77
+ function walkListAttributes(tree, walkFn) {
77
78
  tree.walk((node)=>{
78
79
  if (!node.attrs) {
79
80
  return node;
80
81
  }
81
82
  const tagName = node.tag ? node.tag.toLowerCase() : undefined;
82
- Object.keys(node.attrs).forEach((attrName)=>{
83
+ Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
83
84
  const attrNameLower = attrName.toLowerCase();
84
- if (!isListAttribute(attrNameLower, tagName)) {
85
- return;
86
- }
87
- const attrValues = splitListAttributeValues(node.attrs[attrName]);
88
- if (attrValues.length < 2) {
85
+ if (!isListAttribute(attrNameLower, tagName) || typeof attrValues !== 'string') {
89
86
  return;
90
87
  }
91
- node.attrs[attrName] = attrValues.sort((a, b)=>{
92
- // @ts-expect-error -- deliberately use minus operator to sort things
93
- return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
94
- }).join(' ');
88
+ walkFn(node.attrs, attrName, attrValues);
95
89
  });
96
90
  return node;
97
91
  });
92
+ }
93
+ function sortAttributesWithListsInAlphabeticalOrder(tree) {
94
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
95
+ const values = splitListAttributeValues(attrValues);
96
+ if (values.length < 2) {
97
+ return;
98
+ }
99
+ nodeAttrs[attrName] = values.sort((a, b)=>{
100
+ // @ts-expect-error -- deliberately use minus operator to sort things
101
+ return typeof a.localeCompare === 'function' ? a.localeCompare(b) : a - b;
102
+ }).join(' ');
103
+ });
98
104
  return tree;
99
105
  }
100
106
  function sortAttributesWithListsByFrequency(tree) {
101
- const tokenChainObj = {}; // <attrNameLower: AttributeTokenChain>[]
107
+ const tokenChainObj = {};
102
108
  // Traverse through tree to get frequency
103
- tree.walk((node)=>{
104
- if (!node.attrs) {
105
- return node;
106
- }
107
- const tagName = node.tag ? node.tag.toLowerCase() : undefined;
108
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
109
- const attrNameLower = attrName.toLowerCase();
110
- if (!isListAttribute(attrNameLower, tagName)) {
111
- return;
112
- }
113
- tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new AttributeTokenChain();
114
- tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
115
- });
116
- return node;
109
+ walkListAttributes(tree, (_nodeAttrs, attrName, attrValues)=>{
110
+ const attrNameLower = attrName.toLowerCase();
111
+ tokenChainObj[attrNameLower] = tokenChainObj[attrNameLower] || new ListAttributeTokenChain();
112
+ tokenChainObj[attrNameLower].addFromNodeAttrsArray(splitListAttributeValues(attrValues));
117
113
  });
118
114
  // Traverse through tree again, this time sort the attribute values
119
- tree.walk((node)=>{
120
- if (!node.attrs) {
121
- return node;
115
+ walkListAttributes(tree, (nodeAttrs, attrName, attrValues)=>{
116
+ const attrNameLower = attrName.toLowerCase();
117
+ if (!tokenChainObj[attrNameLower]) {
118
+ return;
122
119
  }
123
- const tagName = node.tag ? node.tag.toLowerCase() : undefined;
124
- Object.entries(node.attrs).forEach(([attrName, attrValues])=>{
125
- const attrNameLower = attrName.toLowerCase();
126
- if (!isListAttribute(attrNameLower, tagName)) {
127
- return;
128
- }
129
- if (tokenChainObj[attrNameLower]) {
130
- node.attrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
131
- }
132
- });
133
- return node;
120
+ nodeAttrs[attrName] = tokenChainObj[attrNameLower].sortFromNodeAttrsArray(splitListAttributeValues(attrValues)).join(' ');
134
121
  });
135
122
  return tree;
136
123
  }