postcss-sort-media-queries 6.0.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
@@ -1,8 +1,9 @@
1
1
  # PostCSS Sort Media Queries
2
2
 
3
3
  [PostCSS]: https://github.com/postcss/postcss
4
- [MIT]: https://github.com/yunusga/postcss-sort-media-queries/blob/master/LICENSE
5
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
6
7
  [Releases history]: https://github.com/yunusga/postcss-sort-media-queries/blob/master/CHANGELOG.md
7
8
 
8
9
  [![npm](https://img.shields.io/npm/v/postcss-sort-media-queries.svg)](https://www.npmjs.com/package/postcss-sort-media-queries) [![Node.js CI](https://github.com/yunusga/postcss-sort-media-queries/actions/workflows/test.yml/badge.svg?branch=main&event=status)](https://github.com/yunusga/postcss-sort-media-queries/actions/workflows/test.yml)
@@ -37,7 +38,7 @@
37
38
 
38
39
  ## Online demo
39
40
 
40
- And here is the [online demo](https://postcss-sort-media-queries.github.io)
41
+ And here is the [Online Demo]
41
42
 
42
43
  ## Examples
43
44
 
@@ -260,8 +261,10 @@ See [Releases history]
260
261
  ## Thanks
261
262
 
262
263
  - Andrey Sitnik [@ai](https://github.com/ai)
263
- - Oleh Dutchenko [@dutchenkoOleg](https://github.com/OlehDutchenko)
264
+ - Oleh Dutchenko [@OlehDutchenko](https://github.com/OlehDutchenko)
264
265
  - Jakub Caban [@Lustmored](https://github.com/Lustmored)
265
266
  - Dmytro Symonov [@Kassaila](https://github.com/Kassaila)
266
267
  - Kai Falkowski [@SassNinja](https://github.com/SassNinja)
267
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
  {
@@ -252,56 +260,65 @@ function plugin(options = {}) {
252
260
  const sortCSSmq = createSort(options.configuration);
253
261
  return {
254
262
  postcssPlugin: "postcss-sort-media-queries",
263
+ // Execute once after the entire tree has been parsed
255
264
  OnceExit(root, { AtRule }) {
256
- let parents = {
257
- root: [],
258
- nested: []
259
- };
260
- let processed = /* @__PURE__ */ Symbol("processed");
265
+ let parents = [];
261
266
  root.walkAtRules("media", (atRule) => {
262
- if (atRule.parent[processed]) {
263
- return;
264
- }
265
- if (atRule.parent.type === "root") {
266
- parents.root.push(atRule.parent);
267
- }
268
- if (atRule.parent.type !== "root") {
269
- 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
+ };
270
274
  }
271
- atRule.parent[processed] = true;
272
275
  return;
273
276
  });
274
- Object.keys(parents).forEach((type) => {
275
- 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) {
276
291
  return;
277
292
  }
278
- parents[type].forEach((parent) => {
279
- let media = parent.nodes.filter(
280
- (n) => n.type === "atrule" && n.name === "media"
281
- );
282
- if (!media) {
283
- return;
284
- }
285
- let atRules = [];
286
- media.forEach((atRule) => {
287
- let query = atRule.params;
288
- if (!atRules[query]) {
289
- atRules[query] = new AtRule({
290
- name: atRule.name,
291
- params: atRule.params,
292
- source: atRule.source
293
- });
294
- }
295
- atRule.nodes.forEach((node) => {
296
- atRules[query].append(node.clone());
297
- });
298
- atRule.remove();
299
- });
300
- if (atRules) {
301
- sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach((query) => {
302
- 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
303
300
  });
304
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);
305
322
  });
306
323
  });
307
324
  }
@@ -0,0 +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;
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.0.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,13 +31,13 @@
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",
38
38
  "exports": {
39
39
  ".": {
40
- "require": "./src/index.cjs",
40
+ "require": "./build/wrapper.cjs",
41
41
  "import": "./src/index.js",
42
42
  "default": "./src/index.js"
43
43
  }
@@ -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.clone());
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;