postcss-sort-media-queries 6.2.2 → 6.3.3
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 +372 -1
- package/build/index.cjs +31 -2
- package/package.json +7 -7
- package/src/index.js +137 -137
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
- [Examples](#examples)
|
|
25
25
|
- [Mobile first sorting](#mobile-first-sorting)
|
|
26
26
|
- [Desktop first sorting](#desktop-first-sorting)
|
|
27
|
+
- [Nested media queries sorting](#nested-media-queries-sorting)
|
|
27
28
|
- [Install](#install)
|
|
28
29
|
- [Usage](#usage)
|
|
29
30
|
- [Options](#options)
|
|
30
31
|
- [sort](#sort)
|
|
31
32
|
- [Custom sort function](#custom-sort-function)
|
|
32
33
|
- [Sort configuration](#sort-configuration)
|
|
33
|
-
- [Only Top Level](#only-top-level)
|
|
34
34
|
- [Changelog](#changelog)
|
|
35
35
|
- [License](#license)
|
|
36
36
|
- [Other PostCSS plugins](#other-postcss-plugins)
|
|
@@ -128,6 +128,377 @@ And here is the [Online Demo]
|
|
|
128
128
|
}
|
|
129
129
|
```
|
|
130
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
|
+
}
|
|
146
|
+
|
|
147
|
+
.print-only-global-5 {
|
|
148
|
+
display: block;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.print-only-global-6 {
|
|
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**
|
|
337
|
+
|
|
338
|
+
```css
|
|
339
|
+
@layer base {
|
|
340
|
+
@media (min-width: 320px) {
|
|
341
|
+
.print-only-320-1 {
|
|
342
|
+
color: black;
|
|
343
|
+
}
|
|
344
|
+
.print-only-320-2 {
|
|
345
|
+
color: black;
|
|
346
|
+
}
|
|
347
|
+
.print-only-320-3 {
|
|
348
|
+
color: black;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
@media (min-width: 620px) {
|
|
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
|
+
```
|
|
501
|
+
|
|
131
502
|
## Install
|
|
132
503
|
|
|
133
504
|
First thing's, install the module:
|
package/build/index.cjs
CHANGED
|
@@ -234,8 +234,37 @@ function createSort(configuration) {
|
|
|
234
234
|
return sortCSSmq;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// node_modules/nanoid/index.js
|
|
238
|
+
var import_node_crypto = require("node:crypto");
|
|
239
|
+
|
|
240
|
+
// node_modules/nanoid/url-alphabet/index.js
|
|
241
|
+
var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
242
|
+
|
|
243
|
+
// node_modules/nanoid/index.js
|
|
244
|
+
var POOL_SIZE_MULTIPLIER = 128;
|
|
245
|
+
var pool;
|
|
246
|
+
var poolOffset;
|
|
247
|
+
function fillPool(bytes) {
|
|
248
|
+
if (!pool || pool.length < bytes) {
|
|
249
|
+
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
|
|
250
|
+
import_node_crypto.webcrypto.getRandomValues(pool);
|
|
251
|
+
poolOffset = 0;
|
|
252
|
+
} else if (poolOffset + bytes > pool.length) {
|
|
253
|
+
import_node_crypto.webcrypto.getRandomValues(pool);
|
|
254
|
+
poolOffset = 0;
|
|
255
|
+
}
|
|
256
|
+
poolOffset += bytes;
|
|
257
|
+
}
|
|
258
|
+
function nanoid(size = 21) {
|
|
259
|
+
fillPool(size |= 0);
|
|
260
|
+
let id = "";
|
|
261
|
+
for (let i = poolOffset - size; i < poolOffset; i++) {
|
|
262
|
+
id += urlAlphabet[pool[i] & 63];
|
|
263
|
+
}
|
|
264
|
+
return id;
|
|
265
|
+
}
|
|
266
|
+
|
|
237
267
|
// src/index.js
|
|
238
|
-
var import_crypto = require("crypto");
|
|
239
268
|
function sortAtRules(queries, options, sortCSSmq) {
|
|
240
269
|
if (typeof options.sort !== "function") {
|
|
241
270
|
options.sort = options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
|
|
@@ -265,7 +294,7 @@ function plugin(options = {}) {
|
|
|
265
294
|
let parents = [];
|
|
266
295
|
root.walkAtRules("media", (atRule) => {
|
|
267
296
|
if (!atRule.parent.groupId) {
|
|
268
|
-
let groupId = (
|
|
297
|
+
let groupId = nanoid();
|
|
269
298
|
atRule.parent.groupId = groupId;
|
|
270
299
|
parents[groupId] = {
|
|
271
300
|
parent: atRule.parent,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-sort-media-queries",
|
|
3
3
|
"description": "PostCSS plugin for sorting and combining CSS media queries with mobile first / **desktop first methodologies",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.3.3",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.0.0"
|
|
7
7
|
},
|
|
@@ -56,19 +56,19 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@eslint/js": "^10.0.1",
|
|
59
|
-
"@vitest/coverage-v8": "^4.0
|
|
60
|
-
"esbuild": "^0.27.
|
|
61
|
-
"eslint": "^10.0
|
|
59
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
60
|
+
"esbuild": "^0.27.4",
|
|
61
|
+
"eslint": "^10.1.0",
|
|
62
62
|
"globals": "^17.4.0",
|
|
63
63
|
"husky": "^9.1.7",
|
|
64
|
-
"postcss": "^8.5.6",
|
|
65
64
|
"prettier": "3.8.1",
|
|
66
|
-
"vitest": "^4.0
|
|
65
|
+
"vitest": "^4.1.0"
|
|
67
66
|
},
|
|
68
67
|
"peerDependencies": {
|
|
68
|
+
"nanoid": "^5.1.6",
|
|
69
69
|
"postcss": "^8.5.6"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"sort-css-media-queries": "^3.0.
|
|
72
|
+
"sort-css-media-queries": "^3.0.3"
|
|
73
73
|
}
|
|
74
74
|
}
|
package/src/index.js
CHANGED
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
import createSort from 'sort-css-media-queries/create-sort';
|
|
2
|
-
import {
|
|
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 = options.sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return queries.sort(options.sort);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getDepth(node) {
|
|
18
|
-
let depth = 0;
|
|
19
|
-
|
|
20
|
-
for (let p = node.parent; p; p = p.parent) {
|
|
21
|
-
depth++;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return depth;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function plugin(options = {}) {
|
|
28
|
-
|
|
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 =
|
|
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((query) => {
|
|
112
|
-
parent.append(atRules[query]);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
root.walkAtRules('media', (parent) => {
|
|
118
|
-
// Filter only @media nodes from the parent's children
|
|
119
|
-
let medias = parent.nodes.filter(
|
|
120
|
-
(node) => node.type === 'atrule' && node.name === 'media'
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
if (!medias) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
medias.forEach((atRule) => {
|
|
128
|
-
parent.append(atRule);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
plugin.postcss = true;
|
|
136
|
-
|
|
137
|
-
export default plugin;
|
|
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 = options.sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return queries.sort(options.sort);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getDepth(node) {
|
|
18
|
+
let depth = 0;
|
|
19
|
+
|
|
20
|
+
for (let p = node.parent; p; p = p.parent) {
|
|
21
|
+
depth++;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return depth;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function plugin(options = {}) {
|
|
28
|
+
|
|
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((query) => {
|
|
112
|
+
parent.append(atRules[query]);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
root.walkAtRules('media', (parent) => {
|
|
118
|
+
// Filter only @media nodes from the parent's children
|
|
119
|
+
let medias = parent.nodes.filter(
|
|
120
|
+
(node) => node.type === 'atrule' && node.name === 'media'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!medias) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
medias.forEach((atRule) => {
|
|
128
|
+
parent.append(atRule);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
plugin.postcss = true;
|
|
136
|
+
|
|
137
|
+
export default plugin;
|