postcss-sort-media-queries 6.3.3 → 6.5.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.
- package/README.md +113 -512
- package/{build → dist}/index.cjs +5 -3
- package/dist/index.d.ts +17 -0
- package/dist/index.js +139 -0
- package/{build → dist}/wrapper.cjs +2 -2
- package/package.json +77 -74
- package/src/index.js +139 -137
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PostCSS Sort Media Queries
|
|
2
2
|
|
|
3
|
-
[PostCSS]:
|
|
4
|
-
[official docs]:
|
|
5
|
-
[Online Demo]:
|
|
6
|
-
[MIT]:
|
|
3
|
+
[PostCSS]: https://github.com/postcss/postcss
|
|
4
|
+
[official docs]: https://github.com/postcss/postcss#usage
|
|
5
|
+
[Online Demo]: https://yunusga.uz/postcss-sort-media-queries/
|
|
6
|
+
[MIT]: https://github.com/yunusga/postcss-sort-media-queries/blob/master/LICENSE
|
|
7
7
|
[Releases history]: https://github.com/yunusga/postcss-sort-media-queries/blob/master/CHANGELOG.md
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/postcss-sort-media-queries) [](https://github.com/yunusga/postcss-sort-media-queries/actions/workflows/test.yml)
|
|
@@ -12,497 +12,35 @@
|
|
|
12
12
|
|
|
13
13
|
<img src="logo.svg?sanitize=true" align="right" title="PostCSS sort media queries logotype" width="100" height="100">
|
|
14
14
|
|
|
15
|
-
🌏 **English** ▫ [**O'zbek**](README-UZ.md)
|
|
15
|
+
🌏 **English** ▫ [**O'zbek**](README-UZ.md) ▫ [**简体中文**](README-ZH.md)
|
|
16
16
|
|
|
17
|
-
[PostCSS] plugin for sorting and combining CSS media queries
|
|
17
|
+
**PostcSS Sort Qedia Queries** is a powerful and flexible [PostCSS] plugin for sorting and combining CSS media queries using **mobile-first** or **desktop-first** methodologies. It helps maintain a clean, predictable stylesheet structure, improves readability, and prevents unexpected style overrides.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
A key advantage of this plugin is its **full support for nested media queries**, ensuring correct processing and ordering even in complex, deeply structured stylesheets. This makes it perfectly suited for modern CSS workflows that rely on nesting via PostCSS or preprocessor-like patterns.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
- [Online demo](#online-demo)
|
|
24
|
-
- [Examples](#examples)
|
|
25
|
-
- [Mobile first sorting](#mobile-first-sorting)
|
|
26
|
-
- [Desktop first sorting](#desktop-first-sorting)
|
|
27
|
-
- [Nested media queries sorting](#nested-media-queries-sorting)
|
|
28
|
-
- [Install](#install)
|
|
29
|
-
- [Usage](#usage)
|
|
30
|
-
- [Options](#options)
|
|
31
|
-
- [sort](#sort)
|
|
32
|
-
- [Custom sort function](#custom-sort-function)
|
|
33
|
-
- [Sort configuration](#sort-configuration)
|
|
34
|
-
- [Changelog](#changelog)
|
|
35
|
-
- [License](#license)
|
|
36
|
-
- [Other PostCSS plugins](#other-postcss-plugins)
|
|
37
|
-
- [Thanks 💪](#thanks)
|
|
38
|
-
|
|
39
|
-
## Online demo
|
|
40
|
-
|
|
41
|
-
And here is the [Online Demo]
|
|
42
|
-
|
|
43
|
-
## Examples
|
|
44
|
-
|
|
45
|
-
### Mobile first sorting
|
|
46
|
-
|
|
47
|
-
**Before**
|
|
48
|
-
|
|
49
|
-
```css
|
|
50
|
-
@media (min-width: 1400px) {}
|
|
51
|
-
@media (min-width: 1200px) {}
|
|
52
|
-
|
|
53
|
-
@layer reset {
|
|
54
|
-
|
|
55
|
-
@media (min-width: 1200px) {
|
|
56
|
-
@media (min-width: 992px) {}
|
|
57
|
-
@media (min-width: 768px) {}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@media (min-width: 768px) {
|
|
61
|
-
@media (min-width: 640px) {}
|
|
62
|
-
@media (min-width: 320px) {}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**After**
|
|
68
|
-
|
|
69
|
-
```css
|
|
70
|
-
@layer reset {
|
|
71
|
-
|
|
72
|
-
@media (min-width: 768px) {
|
|
73
|
-
@media (min-width: 320px) {}
|
|
74
|
-
@media (min-width: 640px) {}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
@media (min-width: 1200px) {
|
|
78
|
-
@media (min-width: 768px) {}
|
|
79
|
-
@media (min-width: 992px) {}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
@media (min-width: 1200px) {}
|
|
84
|
-
@media (min-width: 1400px) {}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Desktop first sorting
|
|
88
|
-
|
|
89
|
-
**Before**
|
|
90
|
-
```css
|
|
91
|
-
@media screen and (width < 640px) {
|
|
92
|
-
.header { color: #cdcdcd }
|
|
93
|
-
}
|
|
94
|
-
@media screen and (min-width: 760px) {
|
|
95
|
-
.desktop-first { color: #cdcdcd }
|
|
96
|
-
}
|
|
97
|
-
@media screen and (width < 640px) {
|
|
98
|
-
.main { color: #cdcdcd }
|
|
99
|
-
}
|
|
100
|
-
@media screen and (min-width: 1280px) {
|
|
101
|
-
.desktop-first { color: #cdcdcd }
|
|
102
|
-
}
|
|
103
|
-
@media screen and (max-width: 760px) {
|
|
104
|
-
.footer { color: #cdcdcd }
|
|
105
|
-
}
|
|
106
|
-
@media screen and (max-width: 640px) {
|
|
107
|
-
.footer { color: #cdcdcd }
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**After**
|
|
112
|
-
|
|
113
|
-
```css
|
|
114
|
-
@media screen and (max-width: 760px) {
|
|
115
|
-
.footer { color: #cdcdcd }
|
|
116
|
-
}
|
|
117
|
-
@media screen and (width < 640px) {
|
|
118
|
-
/* combined */
|
|
119
|
-
.header { color: #cdcdcd }
|
|
120
|
-
.main { color: #cdcdcd }
|
|
121
|
-
.footer { color: #cdcdcd }
|
|
122
|
-
}
|
|
123
|
-
@media screen and (min-width: 760px) {
|
|
124
|
-
.desktop-first { color: #cdcdcd }
|
|
125
|
-
}
|
|
126
|
-
@media screen and (min-width: 1280px) {
|
|
127
|
-
.desktop-first { color: #cdcdcd }
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Nested media queries sorting
|
|
132
|
-
|
|
133
|
-
**Before**
|
|
134
|
-
|
|
135
|
-
```css
|
|
136
|
-
@media (min-width: 710px) {
|
|
137
|
-
.print-only-global-2 {
|
|
138
|
-
display: block;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@media (min-width: 1210px) {
|
|
143
|
-
.print-only-global-4 {
|
|
144
|
-
display: block;
|
|
145
|
-
}
|
|
21
|
+
In addition, the plugin fully supports **CSS Media Queries Level 4**, including modern **range syntax**.
|
|
146
22
|
|
|
147
|
-
|
|
148
|
-
display: block;
|
|
149
|
-
}
|
|
23
|
+
Sorting works based on [OlehDutchenko/sort-css-media-queries](https://github.com/OlehDutchenko/sort-css-media-queries).
|
|
150
24
|
|
|
151
|
-
|
|
152
|
-
display: block;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
@media (min-width: 310px) {
|
|
157
|
-
.print-only-global-1 {
|
|
158
|
-
display: block;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
@media (min-width: 710px) {
|
|
163
|
-
.print-only-global-3 {
|
|
164
|
-
display: block;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@media (min-width: 1210px) {
|
|
169
|
-
.print-only-global-7 {
|
|
170
|
-
display: block;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
@media print {
|
|
175
|
-
.print-only {
|
|
176
|
-
display: block;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.print-only-parent-1 {
|
|
180
|
-
display: block;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@media (orientation: landscape) {
|
|
184
|
-
.print-only {
|
|
185
|
-
color: black;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
@media (min-width: 910px) {
|
|
189
|
-
.nested-landscape-3 {
|
|
190
|
-
color: black;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.nested-landscape-4 {
|
|
194
|
-
color: black;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
.nested-landscape-5 {
|
|
198
|
-
color: black;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
@media (min-width: 810px) {
|
|
203
|
-
.nested-landscape-2 {
|
|
204
|
-
color: black;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
@media (min-width: 710px) {
|
|
209
|
-
.nested-landscape-1 {
|
|
210
|
-
color: black;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.nested-landscape-0 {
|
|
215
|
-
color: black;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
@media (min-width: 710px) {
|
|
219
|
-
.nested-landscape-4 {
|
|
220
|
-
color: black;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
.nested-landscape-5 {
|
|
224
|
-
color: black;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.nested-landscape-6 {
|
|
228
|
-
color: black;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
@media (min-width: 710px) {
|
|
233
|
-
.nested-landscape-8 {
|
|
234
|
-
color: black;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.nested-landscape-9 {
|
|
238
|
-
color: black;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
@media (min-width: 500px) {
|
|
244
|
-
.print-only {
|
|
245
|
-
color: black;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
.print-only-parent-2 {
|
|
250
|
-
display: block;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
@media (min-width: 500px) {
|
|
254
|
-
.print-only {
|
|
255
|
-
color: black;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
@media (orientation: portrait) {
|
|
260
|
-
.print-only {
|
|
261
|
-
color: gray;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.print-only-parent-3 {
|
|
266
|
-
display: block;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
@media (min-width: 320px) {
|
|
270
|
-
.print-only {
|
|
271
|
-
color: black;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
@media (orientation: landscape) {
|
|
276
|
-
.print-only-landscape-1-1 {
|
|
277
|
-
color: gray;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.print-only-landscape-1-2 {
|
|
281
|
-
color: gray;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.print-only-landscape-1-3 {
|
|
285
|
-
color: gray;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
@layer base {
|
|
291
|
-
|
|
292
|
-
@media (min-width: 1220px) {
|
|
293
|
-
.print-only-1200-1 {
|
|
294
|
-
color: black;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
@media (min-width: 320px) {
|
|
299
|
-
.print-only-320-1 {
|
|
300
|
-
color: black;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@media (min-width: 1220px) {
|
|
305
|
-
.print-only-1200-2 {
|
|
306
|
-
color: black;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
@media (min-width: 620px) {
|
|
311
|
-
.print-only-640-1 {
|
|
312
|
-
color: black;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
@media (min-width: 320px) {
|
|
317
|
-
.print-only-320-2 {
|
|
318
|
-
color: black;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
@media (min-width: 320px) {
|
|
323
|
-
.print-only-320-3 {
|
|
324
|
-
color: black;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
@media (min-width: 620px) {
|
|
329
|
-
.print-only-640-2 {
|
|
330
|
-
color: black;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
**After**
|
|
25
|
+
## Table of Contents
|
|
337
26
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
.print-only-640-1 {
|
|
353
|
-
color: black;
|
|
354
|
-
}
|
|
355
|
-
.print-only-640-2 {
|
|
356
|
-
color: black;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
@media (min-width: 1220px) {
|
|
360
|
-
.print-only-1200-1 {
|
|
361
|
-
color: black;
|
|
362
|
-
}
|
|
363
|
-
.print-only-1200-2 {
|
|
364
|
-
color: black;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
@media (min-width: 310px) {
|
|
369
|
-
.print-only-global-1 {
|
|
370
|
-
display: block;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
@media (min-width: 710px) {
|
|
374
|
-
.print-only-global-2 {
|
|
375
|
-
display: block;
|
|
376
|
-
}
|
|
377
|
-
.print-only-global-3 {
|
|
378
|
-
display: block;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
@media (min-width: 1210px) {
|
|
382
|
-
.print-only-global-4 {
|
|
383
|
-
display: block;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
.print-only-global-5 {
|
|
387
|
-
display: block;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.print-only-global-6 {
|
|
391
|
-
display: block;
|
|
392
|
-
}
|
|
393
|
-
.print-only-global-7 {
|
|
394
|
-
display: block;
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
@media print {
|
|
398
|
-
.print-only {
|
|
399
|
-
display: block;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.print-only-parent-1 {
|
|
403
|
-
display: block;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.print-only-parent-2 {
|
|
407
|
-
display: block;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
.print-only-parent-3 {
|
|
411
|
-
display: block;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
@media (min-width: 320px) {
|
|
415
|
-
.print-only {
|
|
416
|
-
color: black;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
@media (min-width: 500px) {
|
|
421
|
-
.print-only {
|
|
422
|
-
color: black;
|
|
423
|
-
}
|
|
424
|
-
.print-only {
|
|
425
|
-
color: black;
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
@media (orientation: landscape) {
|
|
430
|
-
.print-only {
|
|
431
|
-
color: black;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.nested-landscape-0 {
|
|
435
|
-
color: black;
|
|
436
|
-
}
|
|
437
|
-
.print-only-landscape-1-1 {
|
|
438
|
-
color: gray;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
.print-only-landscape-1-2 {
|
|
442
|
-
color: gray;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
.print-only-landscape-1-3 {
|
|
446
|
-
color: gray;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
@media (min-width: 710px) {
|
|
450
|
-
.nested-landscape-1 {
|
|
451
|
-
color: black;
|
|
452
|
-
}
|
|
453
|
-
.nested-landscape-4 {
|
|
454
|
-
color: black;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
.nested-landscape-5 {
|
|
458
|
-
color: black;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
.nested-landscape-6 {
|
|
462
|
-
color: black;
|
|
463
|
-
}
|
|
464
|
-
.nested-landscape-8 {
|
|
465
|
-
color: black;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
.nested-landscape-9 {
|
|
469
|
-
color: black;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
@media (min-width: 810px) {
|
|
474
|
-
.nested-landscape-2 {
|
|
475
|
-
color: black;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
@media (min-width: 910px) {
|
|
480
|
-
.nested-landscape-3 {
|
|
481
|
-
color: black;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
.nested-landscape-4 {
|
|
485
|
-
color: black;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
.nested-landscape-5 {
|
|
489
|
-
color: black;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
@media (orientation: portrait) {
|
|
495
|
-
.print-only {
|
|
496
|
-
color: gray;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
```
|
|
27
|
+
- [Install](#install)
|
|
28
|
+
- [Usage](#usage)
|
|
29
|
+
- [Options](#options)
|
|
30
|
+
- [sort](#sort)
|
|
31
|
+
- [Custom sort function](#custom-sort-function)
|
|
32
|
+
- [Sort configuration](#sort-configuration)
|
|
33
|
+
- [Online demo](#online-demo)
|
|
34
|
+
- [Examples (with nested media queries)](#examples)
|
|
35
|
+
- [Sorting options](#mobile-first-sorting)
|
|
36
|
+
- [Desktop first sorting](#desktop-first-sorting)
|
|
37
|
+
- [Changelog](#changelog)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
- [Other PostCSS plugins](#other-postcss-plugins)
|
|
40
|
+
- [Thanks 💪](#thanks)
|
|
501
41
|
|
|
502
42
|
## Install
|
|
503
43
|
|
|
504
|
-
First thing's, install the module:
|
|
505
|
-
|
|
506
44
|
```
|
|
507
45
|
npm install postcss postcss-sort-media-queries --save-dev
|
|
508
46
|
```
|
|
@@ -513,13 +51,12 @@ Check you project for existed PostCSS config: `postcss.config.js`
|
|
|
513
51
|
in the project root, `"postcss"` section in `package.json`
|
|
514
52
|
or `postcss` in bundle config.
|
|
515
53
|
|
|
516
|
-
|
|
517
54
|
```js
|
|
518
55
|
// CJS
|
|
519
|
-
let sortCssMq = require(
|
|
56
|
+
let sortCssMq = require("postcss-sort-media-queries");
|
|
520
57
|
|
|
521
58
|
// ESM
|
|
522
|
-
import sortCssMq from
|
|
59
|
+
import sortCssMq from "postcss-sort-media-queries";
|
|
523
60
|
```
|
|
524
61
|
|
|
525
62
|
If you already use PostCSS, add the plugin to plugins list:
|
|
@@ -528,14 +65,14 @@ If you already use PostCSS, add the plugin to plugins list:
|
|
|
528
65
|
module.exports = {
|
|
529
66
|
plugins: [
|
|
530
67
|
+ require('postcss-sort-media-queries')({
|
|
531
|
-
+ sort: 'mobile-first'
|
|
68
|
+
+ sort: 'mobile-first' | 'desktop-first' | function // default ('mobile-first')
|
|
532
69
|
+ }),
|
|
533
|
-
require('autoprefixer')
|
|
534
70
|
]
|
|
535
71
|
}
|
|
536
72
|
```
|
|
537
73
|
|
|
538
74
|
or with custom sort function
|
|
75
|
+
|
|
539
76
|
```diff
|
|
540
77
|
module.exports = {
|
|
541
78
|
plugins: [
|
|
@@ -544,7 +81,6 @@ module.exports = {
|
|
|
544
81
|
+ // custom sorting function
|
|
545
82
|
+ }
|
|
546
83
|
+ }),
|
|
547
|
-
require('autoprefixer')
|
|
548
84
|
]
|
|
549
85
|
}
|
|
550
86
|
```
|
|
@@ -554,9 +90,9 @@ and set this plugin in settings.
|
|
|
554
90
|
|
|
555
91
|
## Options
|
|
556
92
|
|
|
557
|
-
> Sorting works based on [OlehDutchenko/sort-css-media-queries](https://github.com/OlehDutchenko/sort-css-media-queries)
|
|
93
|
+
> Sorting works based on [OlehDutchenko/sort-css-media-queries](https://github.com/OlehDutchenko/sort-css-media-queries)
|
|
558
94
|
|
|
559
|
-
###
|
|
95
|
+
### Sort
|
|
560
96
|
|
|
561
97
|
This option support **string** or **function** values.
|
|
562
98
|
|
|
@@ -564,34 +100,25 @@ This option support **string** or **function** values.
|
|
|
564
100
|
- `{string}` `'desktop-first'` - desktop first sorting
|
|
565
101
|
- `{function}` your own sorting function
|
|
566
102
|
|
|
567
|
-
#### `'mobile-first'`
|
|
568
|
-
|
|
569
|
-
```js
|
|
570
|
-
postcss([
|
|
571
|
-
sortMediaQueries({
|
|
572
|
-
sort: 'mobile-first' // default
|
|
573
|
-
})
|
|
574
|
-
]).process(css);
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
#### `'desktop-first'`
|
|
103
|
+
#### `'mobile-first'` or `'desktop-first'`
|
|
578
104
|
|
|
579
105
|
```js
|
|
580
106
|
postcss([
|
|
581
107
|
sortMediaQueries({
|
|
582
|
-
sort:
|
|
583
|
-
})
|
|
108
|
+
sort: "mobile-first" | "desktop-first", // default (mobile-first)
|
|
109
|
+
}),
|
|
584
110
|
]).process(css);
|
|
585
111
|
```
|
|
586
112
|
|
|
587
113
|
### Custom sort function
|
|
114
|
+
|
|
588
115
|
```js
|
|
589
116
|
postcss([
|
|
590
117
|
sortMediaQueries({
|
|
591
118
|
function(a, b) {
|
|
592
119
|
return a.localeCompare(b);
|
|
593
|
-
}
|
|
594
|
-
})
|
|
120
|
+
},
|
|
121
|
+
}),
|
|
595
122
|
]).process(css);
|
|
596
123
|
```
|
|
597
124
|
|
|
@@ -608,13 +135,86 @@ postcss([
|
|
|
608
135
|
sortMediaQueries({
|
|
609
136
|
configuration: {
|
|
610
137
|
unitlessMqAlwaysFirst: true, // or false
|
|
611
|
-
}
|
|
612
|
-
})
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
613
140
|
]).process(css);
|
|
614
141
|
```
|
|
615
142
|
|
|
616
143
|
Or alternatively create a `sort-css-mq.config.json` file in the root of your project. Or add property `sortCssMQ: {}` in your `package.json`.
|
|
617
144
|
|
|
145
|
+
## Online demo
|
|
146
|
+
|
|
147
|
+
And here is the [Online Demo]
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
### Mobile first sorting (with nested media queries)
|
|
152
|
+
|
|
153
|
+
#### Before
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
root
|
|
157
|
+
│
|
|
158
|
+
├── (min-width: 1400px)
|
|
159
|
+
├── (min-width: 1200px)
|
|
160
|
+
│
|
|
161
|
+
└── @layer reset
|
|
162
|
+
│
|
|
163
|
+
├── (min-width: 1200px)
|
|
164
|
+
│ ├── (min-width: 992px)
|
|
165
|
+
│ └── (min-width: 768px)
|
|
166
|
+
│
|
|
167
|
+
└── (min-width: 768px)
|
|
168
|
+
├── (min-width: 640px)
|
|
169
|
+
└── (min-width: 320px)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
#### After
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
root
|
|
176
|
+
│
|
|
177
|
+
├── @layer reset
|
|
178
|
+
│ │
|
|
179
|
+
│ ├── (min-width: 768px)
|
|
180
|
+
│ │ ├── (min-width: 320px)
|
|
181
|
+
│ │ └── (min-width: 640px)
|
|
182
|
+
│ │
|
|
183
|
+
│ └── (min-width: 1200px)
|
|
184
|
+
│ ├── (min-width: 768px)
|
|
185
|
+
│ └── (min-width: 992px)
|
|
186
|
+
│
|
|
187
|
+
├── (min-width: 1200px)
|
|
188
|
+
└── (min-width: 1400px)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Desktop first sorting
|
|
192
|
+
|
|
193
|
+
**Before**
|
|
194
|
+
|
|
195
|
+
```css
|
|
196
|
+
root
|
|
197
|
+
│
|
|
198
|
+
├── (width < 640px)
|
|
199
|
+
├── (min-width: 760px)
|
|
200
|
+
├── (width < 640px)
|
|
201
|
+
├── (min-width: 1280px)
|
|
202
|
+
├── (max-width: 760px)
|
|
203
|
+
└── (max-width: 640px)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**After**
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
root
|
|
210
|
+
│
|
|
211
|
+
├── (min-width: 760px)
|
|
212
|
+
├── (max-width: 640px)
|
|
213
|
+
├── (width < 640px)
|
|
214
|
+
├── (max-width: 760px)
|
|
215
|
+
└── (min-width: 1280px)
|
|
216
|
+
```
|
|
217
|
+
|
|
618
218
|
---
|
|
619
219
|
|
|
620
220
|
## Changelog
|
|
@@ -627,7 +227,7 @@ See [Releases history]
|
|
|
627
227
|
|
|
628
228
|
## Other PostCSS plugins
|
|
629
229
|
|
|
630
|
-
- [`postcss-momentum-scrolling`](https://github.com/solversgroup/postcss-momentum-scrolling) - plugin for adding **momentum** style scrolling behavior (`-webkit-overflow-scrolling:touch`) for elements with overflow (scroll, auto) on iOS
|
|
230
|
+
- [`postcss-momentum-scrolling`](https://github.com/solversgroup/postcss-momentum-scrolling) - plugin for adding **momentum** style scrolling behavior (`-webkit-overflow-scrolling:touch`) for elements with overflow (scroll, auto) on iOS (**deprecated for modern Safari**)
|
|
631
231
|
|
|
632
232
|
## Thanks
|
|
633
233
|
|
|
@@ -639,3 +239,4 @@ See [Releases history]
|
|
|
639
239
|
- Khayot Razzakov [@Khayotbek1](https://github.com/Khayotbek1)
|
|
640
240
|
- ReindDooyeweerd [@ReindDooyeweerd](https://github.com/ReindDooyeweerd)
|
|
641
241
|
- msev [@msev](https://github.com/msev)
|
|
242
|
+
- ajiho [@ajiho](https://github.com/ajiho)
|
package/{build → dist}/index.cjs
RENAMED
|
@@ -334,9 +334,11 @@ function plugin(options = {}) {
|
|
|
334
334
|
atRule.remove();
|
|
335
335
|
});
|
|
336
336
|
if (atRules) {
|
|
337
|
-
sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
|
|
338
|
+
(query) => {
|
|
339
|
+
parent.append(atRules[query]);
|
|
340
|
+
}
|
|
341
|
+
);
|
|
340
342
|
}
|
|
341
343
|
});
|
|
342
344
|
root.walkAtRules("media", (parent) => {
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Plugin } from "postcss";
|
|
2
|
+
|
|
3
|
+
type SortOption =
|
|
4
|
+
| "mobile-first"
|
|
5
|
+
| "desktop-first"
|
|
6
|
+
| ((a: string, b: string) => number);
|
|
7
|
+
|
|
8
|
+
interface Options {
|
|
9
|
+
sort?: SortOption;
|
|
10
|
+
configuration?: {
|
|
11
|
+
unitlessMqAlwaysFirst?: boolean;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
declare const plugin: (options?: Options) => Plugin;
|
|
16
|
+
|
|
17
|
+
export default plugin;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import createSort from "sort-css-media-queries/create-sort";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
|
|
4
|
+
// PostCSS plugin to sort CSS @media rules according to a configurable order.
|
|
5
|
+
// The plugin groups top-level and nested media at-rules, merges rules
|
|
6
|
+
// with identical queries, and re-inserts them in the desired order.
|
|
7
|
+
|
|
8
|
+
// Helper that ensures `options.sort` is a function and sorts queries.
|
|
9
|
+
function sortAtRules(queries, options, sortCSSmq) {
|
|
10
|
+
if (typeof options.sort !== "function") {
|
|
11
|
+
options.sort =
|
|
12
|
+
options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return queries.sort(options.sort);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getDepth(node) {
|
|
19
|
+
let depth = 0;
|
|
20
|
+
|
|
21
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
22
|
+
depth++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return depth;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function plugin(options = {}) {
|
|
29
|
+
// Set default options and allow user overrides
|
|
30
|
+
options = Object.assign(
|
|
31
|
+
{
|
|
32
|
+
sort: "mobile-first",
|
|
33
|
+
configuration: false,
|
|
34
|
+
},
|
|
35
|
+
options,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Create a sorter based on configuration (from sort-css-media-queries)
|
|
39
|
+
const sortCSSmq = createSort(options.configuration);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
postcssPlugin: "postcss-sort-media-queries",
|
|
43
|
+
|
|
44
|
+
// Execute once after the entire tree has been parsed
|
|
45
|
+
OnceExit(root, { AtRule }) {
|
|
46
|
+
// Collect parent nodes that contain media at-rules. We separate
|
|
47
|
+
// top-level (`root`) parents from nested parents so ordering
|
|
48
|
+
// semantics can be preserved independently.
|
|
49
|
+
let parents = [];
|
|
50
|
+
|
|
51
|
+
// Walk all @media at-rules and group their parents
|
|
52
|
+
root.walkAtRules("media", (atRule) => {
|
|
53
|
+
if (!atRule.parent.groupId) {
|
|
54
|
+
let groupId = nanoid();
|
|
55
|
+
|
|
56
|
+
atRule.parent.groupId = groupId;
|
|
57
|
+
|
|
58
|
+
parents[groupId] = {
|
|
59
|
+
parent: atRule.parent,
|
|
60
|
+
depth: getDepth(atRule.parent),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!parents) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parents = Object.fromEntries(
|
|
72
|
+
Object.entries(parents).sort(([, a], [, b]) => {
|
|
73
|
+
return b.depth - a.depth;
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
Object.keys(parents).forEach((groupId) => {
|
|
78
|
+
let { parent } = parents[groupId];
|
|
79
|
+
|
|
80
|
+
// Filter only @media nodes from the parent's children
|
|
81
|
+
let medias = parent.nodes.filter(
|
|
82
|
+
(node) => node.type === "atrule" && node.name === "media",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!medias) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let atRules = [];
|
|
90
|
+
|
|
91
|
+
medias.forEach((atRule) => {
|
|
92
|
+
if (!atRules[atRule.params]) {
|
|
93
|
+
atRules[atRule.params] = new AtRule({
|
|
94
|
+
name: atRule.name,
|
|
95
|
+
params: atRule.params,
|
|
96
|
+
source: atRule.source,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[...atRule.nodes].forEach((node) => {
|
|
101
|
+
atRules[atRule.params].append(node);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Remove the original at-rule since its contents have been
|
|
105
|
+
// merged into `atRules[atRule.params]`.
|
|
106
|
+
atRule.remove();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Sort query keys and append merged at-rules back to the parent
|
|
110
|
+
if (atRules) {
|
|
111
|
+
sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
|
|
112
|
+
(query) => {
|
|
113
|
+
parent.append(atRules[query]);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
root.walkAtRules("media", (parent) => {
|
|
120
|
+
// Filter only @media nodes from the parent's children
|
|
121
|
+
let medias = parent.nodes.filter(
|
|
122
|
+
(node) => node.type === "atrule" && node.name === "media",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!medias) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
medias.forEach((atRule) => {
|
|
130
|
+
parent.append(atRule);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
plugin.postcss = true;
|
|
138
|
+
|
|
139
|
+
export default plugin;
|
package/package.json
CHANGED
|
@@ -1,74 +1,77 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "postcss-sort-media-queries",
|
|
3
|
-
"description": "PostCSS plugin for sorting and combining CSS media queries with mobile first / **desktop first methodologies",
|
|
4
|
-
"version": "6.
|
|
5
|
-
"engines": {
|
|
6
|
-
"node": ">=20.0.0"
|
|
7
|
-
},
|
|
8
|
-
"keywords": [
|
|
9
|
-
"postcss",
|
|
10
|
-
"postcss-plugin",
|
|
11
|
-
"css",
|
|
12
|
-
"css-optimizations",
|
|
13
|
-
"sort",
|
|
14
|
-
"mobile-first",
|
|
15
|
-
"desktop-first",
|
|
16
|
-
"mediaquery",
|
|
17
|
-
"media-queries",
|
|
18
|
-
"mq",
|
|
19
|
-
"responsive-css",
|
|
20
|
-
"combine-media-query",
|
|
21
|
-
"sort-media-query",
|
|
22
|
-
"css-mqpacker",
|
|
23
|
-
"node-css-mqpacker"
|
|
24
|
-
],
|
|
25
|
-
"author": "Yunus Gaziyev <yunusga@yandex.ru>",
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"repository": {
|
|
28
|
-
"type": "git",
|
|
29
|
-
"url": "https://github.com/yunusga/postcss-sort-media-queries.git"
|
|
30
|
-
},
|
|
31
|
-
"bugs": {
|
|
32
|
-
"url": "https://github.com/yunusga/postcss-sort-media-queries/issues"
|
|
33
|
-
},
|
|
34
|
-
"homepage": "https://yunusga.uz/postcss-sort-media-queries",
|
|
35
|
-
"type": "module",
|
|
36
|
-
"main": "src/index.js",
|
|
37
|
-
"module": "src/index.js",
|
|
38
|
-
"exports": {
|
|
39
|
-
".": {
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"test
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
}
|
|
74
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "postcss-sort-media-queries",
|
|
3
|
+
"description": "PostCSS plugin for sorting and combining CSS media queries with mobile first / **desktop first methodologies",
|
|
4
|
+
"version": "6.5.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20.0.0"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"postcss",
|
|
10
|
+
"postcss-plugin",
|
|
11
|
+
"css",
|
|
12
|
+
"css-optimizations",
|
|
13
|
+
"sort",
|
|
14
|
+
"mobile-first",
|
|
15
|
+
"desktop-first",
|
|
16
|
+
"mediaquery",
|
|
17
|
+
"media-queries",
|
|
18
|
+
"mq",
|
|
19
|
+
"responsive-css",
|
|
20
|
+
"combine-media-query",
|
|
21
|
+
"sort-media-query",
|
|
22
|
+
"css-mqpacker",
|
|
23
|
+
"node-css-mqpacker"
|
|
24
|
+
],
|
|
25
|
+
"author": "Yunus Gaziyev <yunusga@yandex.ru>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/yunusga/postcss-sort-media-queries.git"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/yunusga/postcss-sort-media-queries/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://yunusga.uz/postcss-sort-media-queries",
|
|
35
|
+
"type": "module",
|
|
36
|
+
"main": "src/index.js",
|
|
37
|
+
"module": "src/index.js",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"require": "./dist/wrapper.cjs",
|
|
42
|
+
"import": "./dist/index.js",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "npm run build && vitest run",
|
|
51
|
+
"test:watch": "vitest --watch",
|
|
52
|
+
"coverage": "vitest --coverage",
|
|
53
|
+
"lint": "eslint",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"prepare": "husky",
|
|
56
|
+
"build": "npm run build:cjs && cpy \"src/**/*\" dist",
|
|
57
|
+
"build:cjs": "esbuild src/index.js --outfile=dist/index.cjs --format=cjs --bundle --platform=node --external:postcss"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^10.0.1",
|
|
61
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
62
|
+
"cpy-cli": "^7.0.0",
|
|
63
|
+
"esbuild": "^0.28.0",
|
|
64
|
+
"eslint": "^10.2.0",
|
|
65
|
+
"globals": "^17.4.0",
|
|
66
|
+
"husky": "^9.1.7",
|
|
67
|
+
"prettier": "3.8.1",
|
|
68
|
+
"vitest": "^4.1.2"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"nanoid": "^5.1.6",
|
|
72
|
+
"postcss": "^8.5.6"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"sort-css-media-queries": "^3.0.5"
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,137 +1,139 @@
|
|
|
1
|
-
import createSort from
|
|
2
|
-
import { nanoid } from
|
|
3
|
-
|
|
4
|
-
// PostCSS plugin to sort CSS @media rules according to a configurable order.
|
|
5
|
-
// The plugin groups top-level and nested media at-rules, merges rules
|
|
6
|
-
// with identical queries, and re-inserts them in the desired order.
|
|
7
|
-
|
|
8
|
-
// Helper that ensures `options.sort` is a function and sorts queries.
|
|
9
|
-
function sortAtRules(queries, options, sortCSSmq) {
|
|
10
|
-
if (typeof options.sort !==
|
|
11
|
-
options.sort =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Set default options and allow user overrides
|
|
30
|
-
options = Object.assign(
|
|
31
|
-
{
|
|
32
|
-
sort:
|
|
33
|
-
configuration: false,
|
|
34
|
-
},
|
|
35
|
-
options
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
// Create a sorter based on configuration (from sort-css-media-queries)
|
|
39
|
-
const sortCSSmq = createSort(options.configuration);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
postcssPlugin:
|
|
43
|
-
|
|
44
|
-
// Execute once after the entire tree has been parsed
|
|
45
|
-
OnceExit(root, { AtRule }) {
|
|
46
|
-
// Collect parent nodes that contain media at-rules. We separate
|
|
47
|
-
// top-level (`root`) parents from nested parents so ordering
|
|
48
|
-
// semantics can be preserved independently.
|
|
49
|
-
let parents = [];
|
|
50
|
-
|
|
51
|
-
// Walk all @media at-rules and group their parents
|
|
52
|
-
root.walkAtRules(
|
|
53
|
-
if (!atRule.parent.groupId) {
|
|
54
|
-
let groupId = nanoid();
|
|
55
|
-
|
|
56
|
-
atRule.parent.groupId = groupId;
|
|
57
|
-
|
|
58
|
-
parents[groupId] = {
|
|
59
|
-
parent: atRule.parent,
|
|
60
|
-
depth: getDepth(atRule.parent),
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (!parents) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
parents = Object.fromEntries(
|
|
72
|
-
Object.entries(parents).sort(([, a], [, b]) => {
|
|
73
|
-
return b.depth - a.depth;
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
Object.keys(parents).forEach((groupId) => {
|
|
78
|
-
let { parent } = parents[groupId];
|
|
79
|
-
|
|
80
|
-
// Filter only @media nodes from the parent's children
|
|
81
|
-
let medias = parent.nodes.filter(
|
|
82
|
-
(node) => node.type ===
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
if (!medias) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
let atRules = [];
|
|
90
|
-
|
|
91
|
-
medias.forEach((atRule) => {
|
|
92
|
-
if (!atRules[atRule.params]) {
|
|
93
|
-
atRules[atRule.params] = new AtRule({
|
|
94
|
-
name: atRule.name,
|
|
95
|
-
params: atRule.params,
|
|
96
|
-
source: atRule.source,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
[...atRule.nodes].forEach((node) => {
|
|
101
|
-
atRules[atRule.params].append(node);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Remove the original at-rule since its contents have been
|
|
105
|
-
// merged into `atRules[atRule.params]`.
|
|
106
|
-
atRule.remove();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Sort query keys and append merged at-rules back to the parent
|
|
110
|
-
if (atRules) {
|
|
111
|
-
sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
|
|
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
|
-
|
|
1
|
+
import createSort from "sort-css-media-queries/create-sort";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
|
|
4
|
+
// PostCSS plugin to sort CSS @media rules according to a configurable order.
|
|
5
|
+
// The plugin groups top-level and nested media at-rules, merges rules
|
|
6
|
+
// with identical queries, and re-inserts them in the desired order.
|
|
7
|
+
|
|
8
|
+
// Helper that ensures `options.sort` is a function and sorts queries.
|
|
9
|
+
function sortAtRules(queries, options, sortCSSmq) {
|
|
10
|
+
if (typeof options.sort !== "function") {
|
|
11
|
+
options.sort =
|
|
12
|
+
options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return queries.sort(options.sort);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function getDepth(node) {
|
|
19
|
+
let depth = 0;
|
|
20
|
+
|
|
21
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
22
|
+
depth++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return depth;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function plugin(options = {}) {
|
|
29
|
+
// Set default options and allow user overrides
|
|
30
|
+
options = Object.assign(
|
|
31
|
+
{
|
|
32
|
+
sort: "mobile-first",
|
|
33
|
+
configuration: false,
|
|
34
|
+
},
|
|
35
|
+
options,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
// Create a sorter based on configuration (from sort-css-media-queries)
|
|
39
|
+
const sortCSSmq = createSort(options.configuration);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
postcssPlugin: "postcss-sort-media-queries",
|
|
43
|
+
|
|
44
|
+
// Execute once after the entire tree has been parsed
|
|
45
|
+
OnceExit(root, { AtRule }) {
|
|
46
|
+
// Collect parent nodes that contain media at-rules. We separate
|
|
47
|
+
// top-level (`root`) parents from nested parents so ordering
|
|
48
|
+
// semantics can be preserved independently.
|
|
49
|
+
let parents = [];
|
|
50
|
+
|
|
51
|
+
// Walk all @media at-rules and group their parents
|
|
52
|
+
root.walkAtRules("media", (atRule) => {
|
|
53
|
+
if (!atRule.parent.groupId) {
|
|
54
|
+
let groupId = nanoid();
|
|
55
|
+
|
|
56
|
+
atRule.parent.groupId = groupId;
|
|
57
|
+
|
|
58
|
+
parents[groupId] = {
|
|
59
|
+
parent: atRule.parent,
|
|
60
|
+
depth: getDepth(atRule.parent),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!parents) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parents = Object.fromEntries(
|
|
72
|
+
Object.entries(parents).sort(([, a], [, b]) => {
|
|
73
|
+
return b.depth - a.depth;
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
Object.keys(parents).forEach((groupId) => {
|
|
78
|
+
let { parent } = parents[groupId];
|
|
79
|
+
|
|
80
|
+
// Filter only @media nodes from the parent's children
|
|
81
|
+
let medias = parent.nodes.filter(
|
|
82
|
+
(node) => node.type === "atrule" && node.name === "media",
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (!medias) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let atRules = [];
|
|
90
|
+
|
|
91
|
+
medias.forEach((atRule) => {
|
|
92
|
+
if (!atRules[atRule.params]) {
|
|
93
|
+
atRules[atRule.params] = new AtRule({
|
|
94
|
+
name: atRule.name,
|
|
95
|
+
params: atRule.params,
|
|
96
|
+
source: atRule.source,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[...atRule.nodes].forEach((node) => {
|
|
101
|
+
atRules[atRule.params].append(node);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Remove the original at-rule since its contents have been
|
|
105
|
+
// merged into `atRules[atRule.params]`.
|
|
106
|
+
atRule.remove();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Sort query keys and append merged at-rules back to the parent
|
|
110
|
+
if (atRules) {
|
|
111
|
+
sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
|
|
112
|
+
(query) => {
|
|
113
|
+
parent.append(atRules[query]);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
root.walkAtRules("media", (parent) => {
|
|
120
|
+
// Filter only @media nodes from the parent's children
|
|
121
|
+
let medias = parent.nodes.filter(
|
|
122
|
+
(node) => node.type === "atrule" && node.name === "media",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
if (!medias) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
medias.forEach((atRule) => {
|
|
130
|
+
parent.append(atRule);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
plugin.postcss = true;
|
|
138
|
+
|
|
139
|
+
export default plugin;
|