postcss-sort-media-queries 6.1.0 → 6.2.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
@@ -261,8 +261,10 @@ See [Releases history]
261
261
  ## Thanks
262
262
 
263
263
  - Andrey Sitnik [@ai](https://github.com/ai)
264
- - Oleh Dutchenko [@dutchenkoOleg](https://github.com/OlehDutchenko)
264
+ - Oleh Dutchenko [@OlehDutchenko](https://github.com/OlehDutchenko)
265
265
  - Jakub Caban [@Lustmored](https://github.com/Lustmored)
266
266
  - Dmytro Symonov [@Kassaila](https://github.com/Kassaila)
267
267
  - Kai Falkowski [@SassNinja](https://github.com/SassNinja)
268
268
  - Khayot Razzakov [@Khayotbek1](https://github.com/Khayotbek1)
269
+ - ReindDooyeweerd [@ReindDooyeweerd](https://github.com/ReindDooyeweerd)
270
+ - msev [@msev](https://github.com/msev)
package/build/index.cjs CHANGED
@@ -235,12 +235,20 @@ function createSort(configuration) {
235
235
  }
236
236
 
237
237
  // src/index.js
238
+ var import_crypto = require("crypto");
238
239
  function sortAtRules(queries, options, sortCSSmq) {
239
240
  if (typeof options.sort !== "function") {
240
241
  options.sort = options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
241
242
  }
242
243
  return queries.sort(options.sort);
243
244
  }
245
+ function getDepth(node) {
246
+ let depth = 0;
247
+ for (let p = node.parent; p; p = p.parent) {
248
+ depth++;
249
+ }
250
+ return depth;
251
+ }
244
252
  function plugin(options = {}) {
245
253
  options = Object.assign(
246
254
  {
@@ -254,55 +262,63 @@ function plugin(options = {}) {
254
262
  postcssPlugin: "postcss-sort-media-queries",
255
263
  // Execute once after the entire tree has been parsed
256
264
  OnceExit(root, { AtRule }) {
257
- let parents = {
258
- root: [],
259
- nested: []
260
- };
261
- let processed = /* @__PURE__ */ Symbol("processed");
265
+ let parents = [];
262
266
  root.walkAtRules("media", (atRule) => {
263
- if (atRule.parent[processed]) {
264
- return;
265
- }
266
- if (atRule.parent.type === "root") {
267
- parents.root.push(atRule.parent);
268
- }
269
- if (atRule.parent.type !== "root") {
270
- parents.nested.push(atRule.parent);
267
+ if (!atRule.parent.groupId) {
268
+ let groupId = (0, import_crypto.randomUUID)();
269
+ atRule.parent.groupId = groupId;
270
+ parents[groupId] = {
271
+ parent: atRule.parent,
272
+ depth: getDepth(atRule.parent)
273
+ };
271
274
  }
272
- atRule.parent[processed] = true;
273
275
  return;
274
276
  });
275
- Object.keys(parents).forEach((type) => {
276
- if (!parents[type].length) {
277
+ if (!parents) {
278
+ return;
279
+ }
280
+ parents = Object.fromEntries(
281
+ Object.entries(parents).sort(([, a], [, b]) => {
282
+ return b.depth - a.depth;
283
+ })
284
+ );
285
+ Object.keys(parents).forEach((groupId) => {
286
+ let { parent } = parents[groupId];
287
+ let medias = parent.nodes.filter(
288
+ (node) => node.type === "atrule" && node.name === "media"
289
+ );
290
+ if (!medias) {
277
291
  return;
278
292
  }
279
- parents[type].forEach((parent) => {
280
- let media = parent.nodes.filter(
281
- (n) => n.type === "atrule" && n.name === "media"
282
- );
283
- if (!media) {
284
- return;
285
- }
286
- let atRules = [];
287
- media.forEach((atRule) => {
288
- let query = atRule.params;
289
- if (!atRules[query]) {
290
- atRules[query] = new AtRule({
291
- name: atRule.name,
292
- params: atRule.params,
293
- source: atRule.source
294
- });
295
- }
296
- atRule.nodes.forEach((node) => {
297
- atRules[query].append(node);
298
- });
299
- atRule.remove();
300
- });
301
- if (atRules) {
302
- sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach((query) => {
303
- parent.append(atRules[query]);
293
+ let atRules = [];
294
+ medias.forEach((atRule) => {
295
+ if (!atRules[atRule.params]) {
296
+ atRules[atRule.params] = new AtRule({
297
+ name: atRule.name,
298
+ params: atRule.params,
299
+ source: atRule.source
304
300
  });
305
301
  }
302
+ [...atRule.nodes].forEach((node) => {
303
+ atRules[atRule.params].append(node);
304
+ });
305
+ atRule.remove();
306
+ });
307
+ if (atRules) {
308
+ sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach((query) => {
309
+ parent.append(atRules[query]);
310
+ });
311
+ }
312
+ });
313
+ root.walkAtRules("media", (parent) => {
314
+ let medias = parent.nodes.filter(
315
+ (node) => node.type === "atrule" && node.name === "media"
316
+ );
317
+ if (!medias) {
318
+ return;
319
+ }
320
+ medias.forEach((atRule) => {
321
+ parent.append(atRule);
306
322
  });
307
323
  });
308
324
  }
package/build/wrapper.cjs CHANGED
@@ -1,9 +1,9 @@
1
- 'use strict';
2
-
3
- const mod = require('./index.cjs');
4
-
5
- const plugin = mod.default;
6
-
7
- plugin.postcss = mod.postcss;
8
-
9
- module.exports = plugin;
1
+ 'use strict';
2
+
3
+ const mod = require('./index.cjs');
4
+
5
+ const plugin = mod.default;
6
+
7
+ plugin.postcss = mod.postcss;
8
+
9
+ module.exports = plugin;
package/package.json CHANGED
@@ -1,9 +1,9 @@
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.1.0",
4
+ "version": "6.2.2",
5
5
  "engines": {
6
- "node": ">=22.0.0"
6
+ "node": ">=20.0.0"
7
7
  },
8
8
  "keywords": [
9
9
  "postcss",
@@ -31,7 +31,7 @@
31
31
  "bugs": {
32
32
  "url": "https://github.com/yunusga/postcss-sort-media-queries/issues"
33
33
  },
34
- "homepage": "https://github.com/yunusga/postcss-sort-media-queries",
34
+ "homepage": "https://yunusga.uz/postcss-sort-media-queries",
35
35
  "type": "module",
36
36
  "main": "src/index.js",
37
37
  "module": "src/index.js",
@@ -46,15 +46,21 @@
46
46
  "build"
47
47
  ],
48
48
  "scripts": {
49
- "test": "vitest run",
49
+ "test": "npm run build && vitest run",
50
50
  "test:watch": "vitest --watch",
51
51
  "coverage": "vitest --coverage",
52
+ "lint": "eslint",
53
+ "prepare": "husky",
52
54
  "build": "npm run build:cjs",
53
55
  "build:cjs": "esbuild src/index.js --outfile=build/index.cjs --format=cjs --bundle --platform=node --external:postcss"
54
56
  },
55
57
  "devDependencies": {
58
+ "@eslint/js": "^10.0.1",
56
59
  "@vitest/coverage-v8": "^4.0.18",
57
60
  "esbuild": "^0.27.3",
61
+ "eslint": "^10.0.3",
62
+ "globals": "^17.4.0",
63
+ "husky": "^9.1.7",
58
64
  "postcss": "^8.5.6",
59
65
  "prettier": "3.8.1",
60
66
  "vitest": "^4.0.18"
package/src/index.js CHANGED
@@ -1,122 +1,137 @@
1
- import createSort from 'sort-css-media-queries/create-sort';
2
-
3
- // PostCSS plugin to sort CSS @media rules according to a configurable order.
4
- // The plugin groups top-level and nested media at-rules, merges rules
5
- // with identical queries, and re-inserts them in the desired order.
6
-
7
- // Helper that ensures `options.sort` is a function and sorts queries.
8
- function sortAtRules(queries, options, sortCSSmq) {
9
- if (typeof options.sort !== 'function') {
10
- options.sort = options.sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq;
11
- }
12
-
13
- return queries.sort(options.sort);
14
- }
15
-
16
- function plugin(options = {}) {
17
-
18
- // Set default options and allow user overrides
19
- options = Object.assign(
20
- {
21
- sort: 'mobile-first',
22
- configuration: false,
23
- },
24
- options
25
- );
26
-
27
- // Create a sorter based on configuration (from sort-css-media-queries)
28
- const sortCSSmq = createSort(options.configuration);
29
-
30
- return {
31
- postcssPlugin: 'postcss-sort-media-queries',
32
-
33
- // Execute once after the entire tree has been parsed
34
- OnceExit(root, { AtRule }) {
35
- // Collect parent nodes that contain media at-rules. We separate
36
- // top-level (`root`) parents from nested parents so ordering
37
- // semantics can be preserved independently.
38
- let parents = {
39
- root: [],
40
- nested: [],
41
- };
42
-
43
- // Symbol used to mark parents that we've already collected
44
- let processed = Symbol('processed');
45
-
46
- // Walk all @media at-rules and group their parents
47
- root.walkAtRules('media', (atRule) => {
48
- if (atRule.parent[processed]) {
49
- return;
50
- }
51
-
52
- // If the parent is the root of the document, add to root list
53
- if (atRule.parent.type === 'root') {
54
- parents.root.push(atRule.parent);
55
- }
56
-
57
- // Otherwise treat it as a nested parent
58
- if (atRule.parent.type !== 'root') {
59
- parents.nested.push(atRule.parent);
60
- }
61
-
62
- // Mark this parent so we don't collect it twice
63
- atRule.parent[processed] = true;
64
-
65
- return;
66
- });
67
-
68
- // For each parent group, merge and sort its media at-rules
69
- Object.keys(parents).forEach((type) => {
70
- if (!parents[type].length) {
71
- return;
72
- }
73
-
74
- parents[type].forEach((parent) => {
75
- // Filter only @media nodes from the parent's children
76
- let media = parent.nodes.filter(
77
- n => n.type === 'atrule' && n.name === 'media'
78
- );
79
-
80
- if (!media) {
81
- return;
82
- }
83
-
84
- // Combine at-rules with identical query params into a single
85
- // AtRule instance, cloning their children to preserve content.
86
- let atRules = [];
87
-
88
- media.forEach((atRule) => {
89
- let query = atRule.params;
90
-
91
- if (!atRules[query]) {
92
- atRules[query] = new AtRule({
93
- name: atRule.name,
94
- params: atRule.params,
95
- source: atRule.source
96
- });
97
- }
98
-
99
- atRule.nodes.forEach((node) => {
100
- atRules[query].append(node);
101
- });
102
-
103
- // Remove the original at-rule since its contents have been
104
- // merged into `atRules[query]`.
105
- atRule.remove();
106
- });
107
-
108
- // Sort query keys and append merged at-rules back to the parent
109
- if (atRules) {
110
- sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach((query) => {
111
- parent.append(atRules[query]);
112
- });
113
- }
114
- });
115
- });
116
- }
117
- };
118
- }
119
-
120
- plugin.postcss = true;
121
-
122
- export default plugin;
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;