UNPKG

3.48 kBJavaScriptView Raw
1'use strict';
2/**
3 * @param {string | undefined} value
4 * @return {string | undefined}
5 */
6function trimValue(value) {
7 return value ? value.trim() : value;
8}
9
10/**
11 * @param {{nodes: import('postcss').Node[]}} node
12 * @return {boolean}
13 */
14function empty(node) {
15 return !node.nodes.filter((child) => child.type !== 'comment').length;
16}
17
18/**
19 * @param {import('postcss').AnyNode} nodeA
20 * @param {import('postcss').AnyNode} nodeB
21 * @return {boolean}
22 */
23function equals(nodeA, nodeB) {
24 const a = /** @type {any} */ (nodeA);
25 const b = /** @type {any} */ (nodeB);
26 if (a.type !== b.type) {
27 return false;
28 }
29
30 if (a.important !== b.important) {
31 return false;
32 }
33
34 if ((a.raws && !b.raws) || (!a.raws && b.raws)) {
35 return false;
36 }
37
38 switch (a.type) {
39 case 'rule':
40 if (a.selector !== b.selector) {
41 return false;
42 }
43 break;
44 case 'atrule':
45 if (a.name !== b.name || a.params !== b.params) {
46 return false;
47 }
48
49 if (a.raws && trimValue(a.raws.before) !== trimValue(b.raws.before)) {
50 return false;
51 }
52
53 if (
54 a.raws &&
55 trimValue(a.raws.afterName) !== trimValue(b.raws.afterName)
56 ) {
57 return false;
58 }
59 break;
60 case 'decl':
61 if (a.prop !== b.prop || a.value !== b.value) {
62 return false;
63 }
64
65 if (a.raws && trimValue(a.raws.before) !== trimValue(b.raws.before)) {
66 return false;
67 }
68 break;
69 }
70
71 if (a.nodes) {
72 if (a.nodes.length !== b.nodes.length) {
73 return false;
74 }
75
76 for (let i = 0; i < a.nodes.length; i++) {
77 if (!equals(a.nodes[i], b.nodes[i])) {
78 return false;
79 }
80 }
81 }
82 return true;
83}
84
85/**
86 * @param {import('postcss').Rule} last
87 * @param {import('postcss').AnyNode[]} nodes
88 * @return {void}
89 */
90function dedupeRule(last, nodes) {
91 let index = nodes.indexOf(last) - 1;
92 while (index >= 0) {
93 const node = nodes[index--];
94 if (node && node.type === 'rule' && node.selector === last.selector) {
95 last.each((child) => {
96 if (child.type === 'decl') {
97 dedupeNode(child, node.nodes);
98 }
99 });
100
101 if (empty(node)) {
102 node.remove();
103 }
104 }
105 }
106}
107
108/**
109 * @param {import('postcss').AtRule | import('postcss').Declaration} last
110 * @param {import('postcss').AnyNode[]} nodes
111 * @return {void}
112 */
113function dedupeNode(last, nodes) {
114 let index = nodes.includes(last) ? nodes.indexOf(last) - 1 : nodes.length - 1;
115
116 while (index >= 0) {
117 const node = nodes[index--];
118 if (node && equals(node, last)) {
119 node.remove();
120 }
121 }
122}
123
124/**
125 * @param {import('postcss').AnyNode} root
126 * @return {void}
127 */
128function dedupe(root) {
129 const { nodes } =
130 /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
131 root
132 );
133
134 if (!nodes) {
135 return;
136 }
137
138 let index = nodes.length - 1;
139 while (index >= 0) {
140 let last = nodes[index--];
141 if (!last || !last.parent) {
142 continue;
143 }
144 dedupe(last);
145 if (last.type === 'rule') {
146 dedupeRule(last, nodes);
147 } else if (last.type === 'atrule' || last.type === 'decl') {
148 dedupeNode(last, nodes);
149 }
150 }
151}
152
153/**
154 * @type {import('postcss').PluginCreator<void>}
155 * @return {import('postcss').Plugin}
156 */
157function pluginCreator() {
158 return {
159 postcssPlugin: 'postcss-discard-duplicates',
160 OnceExit(css) {
161 dedupe(css);
162 },
163 };
164}
165
166pluginCreator.postcss = true;
167module.exports = pluginCreator;