forked from rehypejs/rehype-minify
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
141 lines (123 loc) · 3.12 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* rehype plugin to sort attribute values.
*
* ## What is this?
*
* This package is a plugin that sorts attribute values, which optimizes for
* repetition-based compression (such as GZip).
*
* ## When should I use this?
*
* You can use this plugin when you want to improve the transfer size of HTML
* documents.
*
* ## API
*
* ### `unified().use(rehypeSortAttributeValues)`
*
* Sort attribute values.
* There are no options.
*
* @example
* <div class="qux quux foo bar"></div>
*/
/**
* @typedef {import('hast').Root} Root
*/
import {visit} from 'unist-util-visit'
import {isElement} from 'hast-util-is-element'
import {schema} from './schema.js'
const own = {}.hasOwnProperty
/**
* Sort attribute values.
*
* @type {import('unified').Plugin<Array<void>, Root>}
*/
export default function rehypeSortAttributeValues() {
return (tree) => {
/** @type {Record<string, {known: Array<string>, counts: Record<string, number>}>} */
const counts = {}
/** @type {Array<[Array<string|number>, string]>} */
const queues = []
visit(tree, 'element', (node) => {
const props = node.properties || {}
/** @type {string} */
let prop
for (prop in props) {
if (own.call(props, prop)) {
const value = props[prop]
if (
own.call(schema, prop) &&
isElement(node, schema[prop]) &&
Array.isArray(value)
) {
add(prop, value)
}
}
}
})
flush()
/**
* @param {string} prop
* @param {Array<string|number>} values
*/
function add(prop, values) {
const cache = counts[prop] || (counts[prop] = {known: [], counts: {}})
let index = -1
while (++index < values.length) {
const value = safe(values[index])
if (value in cache.counts) {
cache.counts[value]++
} else {
cache.counts[value] = 1
cache.known.push(String(values[index]))
}
}
queues.push([values, prop])
}
function flush() {
/** @type {Record<string, Array<string|number>>} */
const caches = {}
/** @type {string} */
let prop
for (prop in counts) {
if (own.call(counts, prop)) {
const values = counts[prop]
caches[prop] = values.known.sort(
(a, b) =>
values.counts[safe(b)] - values.counts[safe(a)] ||
compare(a, b, 0)
)
}
}
let index = -1
while (++index < queues.length) {
const queue = queues[index]
const cache = caches[queue[1]]
queue[0].sort((a, b) => cache.indexOf(a) - cache.indexOf(b))
}
}
}
}
/**
* @param {string|number} value
* @returns {string}
*/
function safe(value) {
return '$' + value
}
/**
* This would create an infinite loop if `a` and `b` could be equal, but the
* list we operate on only has unique values.
*
* @param {string} a
* @param {string} b
* @param {number} index
* @returns {number}
*/
function compare(a, b, index) {
return (
(a.charCodeAt(index) || 0) - (b.charCodeAt(index) || 0) ||
compare(a, b, index + 1)
)
}