| [9af201e] | 1 | export default function collapseDuplicateDeclarations() {
|
|---|
| 2 | return (root) => {
|
|---|
| 3 | root.walkRules((node) => {
|
|---|
| 4 | let seen = new Map()
|
|---|
| 5 | let droppable = new Set([])
|
|---|
| 6 | let byProperty = new Map()
|
|---|
| 7 |
|
|---|
| 8 | node.walkDecls((decl) => {
|
|---|
| 9 | // This could happen if we have nested selectors. In that case the
|
|---|
| 10 | // parent will loop over all its declarations but also the declarations
|
|---|
| 11 | // of nested rules. With this we ensure that we are shallowly checking
|
|---|
| 12 | // declarations.
|
|---|
| 13 | if (decl.parent !== node) {
|
|---|
| 14 | return
|
|---|
| 15 | }
|
|---|
| 16 |
|
|---|
| 17 | if (seen.has(decl.prop)) {
|
|---|
| 18 | // Exact same value as what we have seen so far
|
|---|
| 19 | if (seen.get(decl.prop).value === decl.value) {
|
|---|
| 20 | // Keep the last one, drop the one we've seen so far
|
|---|
| 21 | droppable.add(seen.get(decl.prop))
|
|---|
| 22 | // Override the existing one with the new value. This is necessary
|
|---|
| 23 | // so that if we happen to have more than one declaration with the
|
|---|
| 24 | // same value, that we keep removing the previous one. Otherwise we
|
|---|
| 25 | // will only remove the *first* one.
|
|---|
| 26 | seen.set(decl.prop, decl)
|
|---|
| 27 | return
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | // Not the same value, so we need to check if we can merge it so
|
|---|
| 31 | // let's collect it first.
|
|---|
| 32 | if (!byProperty.has(decl.prop)) {
|
|---|
| 33 | byProperty.set(decl.prop, new Set())
|
|---|
| 34 | }
|
|---|
| 35 |
|
|---|
| 36 | byProperty.get(decl.prop).add(seen.get(decl.prop))
|
|---|
| 37 | byProperty.get(decl.prop).add(decl)
|
|---|
| 38 | }
|
|---|
| 39 |
|
|---|
| 40 | seen.set(decl.prop, decl)
|
|---|
| 41 | })
|
|---|
| 42 |
|
|---|
| 43 | // Drop all the duplicate declarations with the exact same value we've
|
|---|
| 44 | // already seen so far.
|
|---|
| 45 | for (let decl of droppable) {
|
|---|
| 46 | decl.remove()
|
|---|
| 47 | }
|
|---|
| 48 |
|
|---|
| 49 | // Analyze the declarations based on its unit, drop all the declarations
|
|---|
| 50 | // with the same unit but the last one in the list.
|
|---|
| 51 | for (let declarations of byProperty.values()) {
|
|---|
| 52 | let byUnit = new Map()
|
|---|
| 53 |
|
|---|
| 54 | for (let decl of declarations) {
|
|---|
| 55 | let unit = resolveUnit(decl.value)
|
|---|
| 56 | if (unit === null) {
|
|---|
| 57 | // We don't have a unit, so should never try and collapse this
|
|---|
| 58 | // value. This is because we can't know how to do it in a correct
|
|---|
| 59 | // way (e.g.: overrides for older browsers)
|
|---|
| 60 | continue
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | if (!byUnit.has(unit)) {
|
|---|
| 64 | byUnit.set(unit, new Set())
|
|---|
| 65 | }
|
|---|
| 66 |
|
|---|
| 67 | byUnit.get(unit).add(decl)
|
|---|
| 68 | }
|
|---|
| 69 |
|
|---|
| 70 | for (let declarations of byUnit.values()) {
|
|---|
| 71 | // Get all but the last one
|
|---|
| 72 | let removableDeclarations = Array.from(declarations).slice(0, -1)
|
|---|
| 73 |
|
|---|
| 74 | for (let decl of removableDeclarations) {
|
|---|
| 75 | decl.remove()
|
|---|
| 76 | }
|
|---|
| 77 | }
|
|---|
| 78 | }
|
|---|
| 79 | })
|
|---|
| 80 | }
|
|---|
| 81 | }
|
|---|
| 82 |
|
|---|
| 83 | let UNITLESS_NUMBER = Symbol('unitless-number')
|
|---|
| 84 |
|
|---|
| 85 | function resolveUnit(input) {
|
|---|
| 86 | let result = /^-?\d*.?\d+([\w%]+)?$/g.exec(input)
|
|---|
| 87 |
|
|---|
| 88 | if (result) {
|
|---|
| 89 | return result[1] ?? UNITLESS_NUMBER
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | return null
|
|---|
| 93 | }
|
|---|