postcss-sort-media-queries 6.4.4 → 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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # PostCSS Sort Media Queries
2
2
 
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
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
  [![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)
@@ -12,7 +12,7 @@
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
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
 
@@ -24,24 +24,23 @@ Sorting works based on [OlehDutchenko/sort-css-media-queries](https://github.com
24
24
 
25
25
  ## Table of Contents
26
26
 
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)
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)
41
41
 
42
42
  ## Install
43
43
 
44
-
45
44
  ```
46
45
  npm install postcss postcss-sort-media-queries --save-dev
47
46
  ```
@@ -52,13 +51,12 @@ Check you project for existed PostCSS config: `postcss.config.js`
52
51
  in the project root, `"postcss"` section in `package.json`
53
52
  or `postcss` in bundle config.
54
53
 
55
-
56
54
  ```js
57
55
  // CJS
58
- let sortCssMq = require('postcss-sort-media-queries');
56
+ let sortCssMq = require("postcss-sort-media-queries");
59
57
 
60
58
  // ESM
61
- import sortCssMq from 'postcss-sort-media-queries';
59
+ import sortCssMq from "postcss-sort-media-queries";
62
60
  ```
63
61
 
64
62
  If you already use PostCSS, add the plugin to plugins list:
@@ -74,6 +72,7 @@ module.exports = {
74
72
  ```
75
73
 
76
74
  or with custom sort function
75
+
77
76
  ```diff
78
77
  module.exports = {
79
78
  plugins: [
@@ -106,18 +105,20 @@ This option support **string** or **function** values.
106
105
  ```js
107
106
  postcss([
108
107
  sortMediaQueries({
109
- sort: 'mobile-first' | 'desktop-first' // default (mobile-first)
110
- })
108
+ sort: "mobile-first" | "desktop-first", // default (mobile-first)
109
+ }),
111
110
  ]).process(css);
112
111
  ```
112
+
113
113
  ### Custom sort function
114
+
114
115
  ```js
115
116
  postcss([
116
117
  sortMediaQueries({
117
118
  function(a, b) {
118
119
  return a.localeCompare(b);
119
- }
120
- })
120
+ },
121
+ }),
121
122
  ]).process(css);
122
123
  ```
123
124
 
@@ -134,8 +135,8 @@ postcss([
134
135
  sortMediaQueries({
135
136
  configuration: {
136
137
  unitlessMqAlwaysFirst: true, // or false
137
- }
138
- })
138
+ },
139
+ }),
139
140
  ]).process(css);
140
141
  ```
141
142
 
@@ -190,6 +191,7 @@ root
190
191
  ### Desktop first sorting
191
192
 
192
193
  **Before**
194
+
193
195
  ```css
194
196
  root
195
197
 
@@ -237,3 +239,4 @@ See [Releases history]
237
239
  - Khayot Razzakov [@Khayotbek1](https://github.com/Khayotbek1)
238
240
  - ReindDooyeweerd [@ReindDooyeweerd](https://github.com/ReindDooyeweerd)
239
241
  - msev [@msev](https://github.com/msev)
242
+ - ajiho [@ajiho](https://github.com/ajiho)
@@ -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((query) => {
338
- parent.append(atRules[query]);
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) => {
@@ -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;
@@ -1,6 +1,6 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const mod = require('./index.cjs');
3
+ const mod = require("./index.cjs");
4
4
 
5
5
  const plugin = mod.default;
6
6
 
package/package.json CHANGED
@@ -1,75 +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.4.4",
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": "./src/index.d.ts",
41
- "require": "./build/wrapper.cjs",
42
- "import": "./src/index.js",
43
- "default": "./src/index.js"
44
- }
45
- },
46
- "files": [
47
- "build"
48
- ],
49
- "scripts": {
50
- "test": "npm run build && vitest run",
51
- "test:watch": "vitest --watch",
52
- "coverage": "vitest --coverage",
53
- "lint": "eslint",
54
- "prepare": "husky",
55
- "build": "npm run build:cjs",
56
- "build:cjs": "esbuild src/index.js --outfile=build/index.cjs --format=cjs --bundle --platform=node --external:postcss"
57
- },
58
- "devDependencies": {
59
- "@eslint/js": "^10.0.1",
60
- "@vitest/coverage-v8": "^4.1.2",
61
- "esbuild": "^0.28.0",
62
- "eslint": "^10.2.0",
63
- "globals": "^17.4.0",
64
- "husky": "^9.1.7",
65
- "prettier": "3.8.1",
66
- "vitest": "^4.1.2"
67
- },
68
- "peerDependencies": {
69
- "nanoid": "^5.1.6",
70
- "postcss": "^8.5.6"
71
- },
72
- "dependencies": {
73
- "sort-css-media-queries": "^3.0.3"
74
- }
75
- }
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,5 +1,5 @@
1
- import createSort from 'sort-css-media-queries/create-sort';
2
- import { nanoid } from 'nanoid';
1
+ import createSort from "sort-css-media-queries/create-sort";
2
+ import { nanoid } from "nanoid";
3
3
 
4
4
  // PostCSS plugin to sort CSS @media rules according to a configurable order.
5
5
  // The plugin groups top-level and nested media at-rules, merges rules
@@ -7,8 +7,9 @@ import { nanoid } from 'nanoid';
7
7
 
8
8
  // Helper that ensures `options.sort` is a function and sorts queries.
9
9
  function sortAtRules(queries, options, sortCSSmq) {
10
- if (typeof options.sort !== 'function') {
11
- options.sort = options.sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq;
10
+ if (typeof options.sort !== "function") {
11
+ options.sort =
12
+ options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
12
13
  }
13
14
 
14
15
  return queries.sort(options.sort);
@@ -25,21 +26,20 @@ function getDepth(node) {
25
26
  }
26
27
 
27
28
  function plugin(options = {}) {
28
-
29
29
  // Set default options and allow user overrides
30
30
  options = Object.assign(
31
31
  {
32
- sort: 'mobile-first',
32
+ sort: "mobile-first",
33
33
  configuration: false,
34
34
  },
35
- options
35
+ options,
36
36
  );
37
37
 
38
38
  // Create a sorter based on configuration (from sort-css-media-queries)
39
39
  const sortCSSmq = createSort(options.configuration);
40
40
 
41
41
  return {
42
- postcssPlugin: 'postcss-sort-media-queries',
42
+ postcssPlugin: "postcss-sort-media-queries",
43
43
 
44
44
  // Execute once after the entire tree has been parsed
45
45
  OnceExit(root, { AtRule }) {
@@ -49,7 +49,7 @@ function plugin(options = {}) {
49
49
  let parents = [];
50
50
 
51
51
  // Walk all @media at-rules and group their parents
52
- root.walkAtRules('media', (atRule) => {
52
+ root.walkAtRules("media", (atRule) => {
53
53
  if (!atRule.parent.groupId) {
54
54
  let groupId = nanoid();
55
55
 
@@ -58,7 +58,7 @@ function plugin(options = {}) {
58
58
  parents[groupId] = {
59
59
  parent: atRule.parent,
60
60
  depth: getDepth(atRule.parent),
61
- }
61
+ };
62
62
  }
63
63
 
64
64
  return;
@@ -71,7 +71,7 @@ function plugin(options = {}) {
71
71
  parents = Object.fromEntries(
72
72
  Object.entries(parents).sort(([, a], [, b]) => {
73
73
  return b.depth - a.depth;
74
- })
74
+ }),
75
75
  );
76
76
 
77
77
  Object.keys(parents).forEach((groupId) => {
@@ -79,7 +79,7 @@ function plugin(options = {}) {
79
79
 
80
80
  // Filter only @media nodes from the parent's children
81
81
  let medias = parent.nodes.filter(
82
- (node) => node.type === 'atrule' && node.name === 'media'
82
+ (node) => node.type === "atrule" && node.name === "media",
83
83
  );
84
84
 
85
85
  if (!medias) {
@@ -108,16 +108,18 @@ function plugin(options = {}) {
108
108
 
109
109
  // Sort query keys and append merged at-rules back to the parent
110
110
  if (atRules) {
111
- sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach((query) => {
112
- parent.append(atRules[query]);
113
- });
111
+ sortAtRules(Object.keys(atRules), options, sortCSSmq).forEach(
112
+ (query) => {
113
+ parent.append(atRules[query]);
114
+ },
115
+ );
114
116
  }
115
117
  });
116
118
 
117
- root.walkAtRules('media', (parent) => {
119
+ root.walkAtRules("media", (parent) => {
118
120
  // Filter only @media nodes from the parent's children
119
121
  let medias = parent.nodes.filter(
120
- (node) => node.type === 'atrule' && node.name === 'media'
122
+ (node) => node.type === "atrule" && node.name === "media",
121
123
  );
122
124
 
123
125
  if (!medias) {
@@ -128,7 +130,7 @@ function plugin(options = {}) {
128
130
  parent.append(atRule);
129
131
  });
130
132
  });
131
- }
133
+ },
132
134
  };
133
135
  }
134
136