postcss-sort-media-queries 6.2.2 → 6.3.2

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 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 = (0, import_crypto.randomUUID)();
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.2.2",
4
+ "version": "6.3.2",
5
5
  "engines": {
6
6
  "node": ">=20.0.0"
7
7
  },
@@ -61,11 +61,11 @@
61
61
  "eslint": "^10.0.3",
62
62
  "globals": "^17.4.0",
63
63
  "husky": "^9.1.7",
64
- "postcss": "^8.5.6",
65
64
  "prettier": "3.8.1",
66
65
  "vitest": "^4.0.18"
67
66
  },
68
67
  "peerDependencies": {
68
+ "nanoid": "^5.1.6",
69
69
  "postcss": "^8.5.6"
70
70
  },
71
71
  "dependencies": {
package/src/index.js CHANGED
@@ -1,137 +1,137 @@
1
- import createSort from 'sort-css-media-queries/create-sort';
2
- import { randomUUID } from 'crypto';
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 = randomUUID();
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;