postcss-sort-media-queries 5.2.0 → 6.1.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,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)
@@ -15,7 +16,7 @@
15
16
 
16
17
  [PostCSS] plugin for sorting and combining CSS media queries with **mobile first** / **desktop first** methodologies.
17
18
 
18
- > From 5.0.0 plugin support [Media Feature Types: “range”](https://www.w3.org/TR/mediaqueries-4/#mq-ranges)
19
+ > From v6.0.0 plugin supported nested media queries and ESM usage
19
20
 
20
21
  ## Table of Contents
21
22
 
@@ -37,58 +38,55 @@
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
 
44
45
  ### Mobile first sorting
45
46
 
46
- **before**
47
+ **Before**
47
48
 
48
49
  ```css
49
- @media screen and (max-width: 640px) {
50
- .head { color: #cfcfcf }
51
- }
52
- @media screen and (max-width: 768px) {
53
- .footer { color: #cfcfcf }
54
- }
55
- @media screen and (max-width: 640px) {
56
- .main { color: #cfcfcf }
57
- }
58
- @media screen and (min-width: 1280px) {
59
- .mobile-first { color: #cfcfcf }
60
- }
61
- @media screen and (width > 640px) {
62
- .mobile-first { color: #cfcfcf }
63
- }
64
- @media screen and (max-width: 640px) {
65
- .footer { color: #cfcfcf }
50
+ @media (min-width: 1400px) {}
51
+ @media (min-width: 1200px) {}
52
+
53
+ @layer reset {
54
+
55
+ @media (min-width: 1200px) {
56
+ @media (min-width: 992px) {}
57
+ @media (min-width: 768px) {}
58
+ }
59
+
60
+ @media (min-width: 768px) {
61
+ @media (min-width: 640px) {}
62
+ @media (min-width: 320px) {}
63
+ }
66
64
  }
67
65
  ```
68
66
 
69
- **after**
67
+ **After**
70
68
 
71
69
  ```css
72
- @media screen and (width > 640px) {
73
- .mobile-first { color: #cfcfcf }
74
- }
75
- @media screen and (min-width: 1280px) {
76
- .mobile-first { color: #cfcfcf }
77
- }
78
- @media screen and (max-width: 768px) {
79
- .footer { color: #cfcfcf }
80
- }
81
- @media screen and (max-width: 640px) {
82
- /* combined */
83
- .head { color: #cfcfcf }
84
- .main { color: #cfcfcf }
85
- .footer { color: #cfcfcf }
70
+ @layer reset {
71
+
72
+ @media (min-width: 768px) {
73
+ @media (min-width: 320px) {}
74
+ @media (min-width: 640px) {}
75
+ }
76
+
77
+ @media (min-width: 1200px) {
78
+ @media (min-width: 768px) {}
79
+ @media (min-width: 992px) {}
80
+ }
86
81
  }
82
+
83
+ @media (min-width: 1200px) {}
84
+ @media (min-width: 1400px) {}
87
85
  ```
88
86
 
89
87
  ### Desktop first sorting
90
88
 
91
- **before**
89
+ **Before**
92
90
  ```css
93
91
  @media screen and (width < 640px) {
94
92
  .header { color: #cdcdcd }
@@ -110,7 +108,7 @@ And here is the [online demo](https://postcss-sort-media-queries.github.io)
110
108
  }
111
109
  ```
112
110
 
113
- **after**
111
+ **After**
114
112
 
115
113
  ```css
116
114
  @media screen and (max-width: 760px) {
@@ -144,6 +142,15 @@ Check you project for existed PostCSS config: `postcss.config.js`
144
142
  in the project root, `"postcss"` section in `package.json`
145
143
  or `postcss` in bundle config.
146
144
 
145
+
146
+ ```js
147
+ // CJS
148
+ let sortCssMq = require('postcss-sort-media-queries');
149
+
150
+ // ESM
151
+ import sortCssMq from 'postcss-sort-media-queries';
152
+ ```
153
+
147
154
  If you already use PostCSS, add the plugin to plugins list:
148
155
 
149
156
  ```diff
@@ -176,7 +183,7 @@ and set this plugin in settings.
176
183
 
177
184
  ## Options
178
185
 
179
- > Sorting works based on [dutchenkoOleg/sort-css-media-queries](https://github.com/dutchenkoOleg/sort-css-media-queries) function.
186
+ > Sorting works based on [OlehDutchenko/sort-css-media-queries](https://github.com/OlehDutchenko/sort-css-media-queries) function.
180
187
 
181
188
  ### sort
182
189
 
@@ -237,18 +244,6 @@ postcss([
237
244
 
238
245
  Or alternatively create a `sort-css-mq.config.json` file in the root of your project. Or add property `sortCssMQ: {}` in your `package.json`.
239
246
 
240
- ### Only Top Level
241
-
242
- Sort only top level media queries to prevent eject nested media queries from parent node
243
-
244
- ```js
245
- postcss([
246
- sortMediaQueries({
247
- onlyTopLevel: true,
248
- })
249
- ]).process(css);
250
- ```
251
-
252
247
  ---
253
248
 
254
249
  ## Changelog
@@ -266,7 +261,7 @@ See [Releases history]
266
261
  ## Thanks
267
262
 
268
263
  - Andrey Sitnik [@ai](https://github.com/ai)
269
- - Oleh Dutchenko [@dutchenkoOleg](https://github.com/dutchenkoOleg)
264
+ - Oleh Dutchenko [@dutchenkoOleg](https://github.com/OlehDutchenko)
270
265
  - Jakub Caban [@Lustmored](https://github.com/Lustmored)
271
266
  - Dmytro Symonov [@Kassaila](https://github.com/Kassaila)
272
267
  - Kai Falkowski [@SassNinja](https://github.com/SassNinja)
@@ -0,0 +1,312 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.js
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ default: () => index_default
23
+ });
24
+ module.exports = __toCommonJS(index_exports);
25
+
26
+ // node_modules/sort-css-media-queries/lib/create-sort.js
27
+ var minMaxWidth = /(!?\(\s*min(-device)?-width)(.|\n)+\(\s*max(-device)?-width|\(\s*width\s*>(=)?(.|\n)+\(\s*width\s*<(=)?|(!?\(.*<(=)?\s*width\s*<(=)?)/i;
28
+ var minWidth = /\(\s*min(-device)?-width|\(\s*width\s*>(=)?/i;
29
+ var maxMinWidth = /(!?\(\s*max(-device)?-width)(.|\n)+\(\s*min(-device)?-width|\(\s*width\s*<(=)?(.|\n)+\(\s*width\s*>(=)?|(!?\(.*>(=)?\s*width\s*>(=)?)/i;
30
+ var maxWidth = /\(\s*max(-device)?-width|\(\s*width\s*<(=)?/i;
31
+ var isMinWidth = _testQuery(minMaxWidth, maxMinWidth, minWidth);
32
+ var isMaxWidth = _testQuery(maxMinWidth, minMaxWidth, maxWidth);
33
+ var minMaxHeight = /(!?\(\s*min(-device)?-height)(.|\n)+\(\s*max(-device)?-height|\(\s*height\s*>(=)?(.|\n)+\(\s*height\s*<(=)?|(!?\(.*<(=)?\s*height\s*<(=)?)/i;
34
+ var minHeight = /\(\s*min(-device)?-height|\(\s*height\s*>(=)?/i;
35
+ var maxMinHeight = /(!?\(\s*max(-device)?-height)(.|\n)+\(\s*min(-device)?-height|\(\s*height\s*<(=)?(.|\n)+\(\s*height\s*>(=)?|(!?\(.*>(=)?\s*height\s*>(=)?)/i;
36
+ var maxHeight = /\(\s*max(-device)?-height|\(\s*height\s*<(=)?/i;
37
+ var isMinHeight = _testQuery(minMaxHeight, maxMinHeight, minHeight);
38
+ var isMaxHeight = _testQuery(maxMinHeight, minMaxHeight, maxHeight);
39
+ var lessThan = /<(?!=)/;
40
+ var grtrThan = />(?!=)/;
41
+ var isPrint = /print/i;
42
+ var isPrintOnly = /^print$/i;
43
+ var maxValue = Number.MAX_VALUE;
44
+ function _getQueryLength(query) {
45
+ let length = /(-?\d*\.?\d+)(ch|em|ex|px|rem)/.exec(query);
46
+ if (length === null && (isMinWidth(query) || isMinHeight(query))) {
47
+ length = /(\d)/.exec(query);
48
+ }
49
+ if (length === "0") {
50
+ return 0;
51
+ }
52
+ if (length === null) {
53
+ return maxValue;
54
+ }
55
+ let number = length[1];
56
+ const unit = length[2];
57
+ switch (unit) {
58
+ case "ch":
59
+ number = parseFloat(number) * 8.8984375;
60
+ break;
61
+ case "em":
62
+ case "rem":
63
+ number = parseFloat(number) * 16;
64
+ break;
65
+ case "ex":
66
+ number = parseFloat(number) * 8.296875;
67
+ break;
68
+ case "px":
69
+ number = parseFloat(number);
70
+ break;
71
+ }
72
+ return +number;
73
+ }
74
+ function _testQuery(doubleTestTrue, doubleTestFalse, singleTest) {
75
+ return function(query) {
76
+ let result;
77
+ if (doubleTestTrue.test(query)) result = true;
78
+ else if (doubleTestFalse.test(query)) result = false;
79
+ else result = singleTest.test(query);
80
+ return query.includes("not") ? !result : result;
81
+ };
82
+ }
83
+ function _testIsPrint(a, b) {
84
+ const isPrintA = isPrint.test(a);
85
+ const isPrintOnlyA = isPrintOnly.test(a);
86
+ const isPrintB = isPrint.test(b);
87
+ const isPrintOnlyB = isPrintOnly.test(b);
88
+ if (isPrintA && isPrintB) {
89
+ if (!isPrintOnlyA && isPrintOnlyB) {
90
+ return 1;
91
+ }
92
+ if (isPrintOnlyA && !isPrintOnlyB) {
93
+ return -1;
94
+ }
95
+ return a.localeCompare(b);
96
+ }
97
+ if (isPrintA) {
98
+ return 1;
99
+ }
100
+ if (isPrintB) {
101
+ return -1;
102
+ }
103
+ return null;
104
+ }
105
+ function createSort(configuration) {
106
+ const config = configuration || {};
107
+ const UNITLESS_MQ_ALWAYS_FIRST = config.unitlessMqAlwaysFirst;
108
+ function sortCSSmq(a, b) {
109
+ const testIsPrint = _testIsPrint(a, b);
110
+ if (testIsPrint !== null) {
111
+ return testIsPrint;
112
+ }
113
+ const minA = isMinWidth(a) || isMinHeight(a);
114
+ const maxA = isMaxWidth(a) || isMaxHeight(a);
115
+ const minB = isMinWidth(b) || isMinHeight(b);
116
+ const maxB = isMaxWidth(b) || isMaxHeight(b);
117
+ if (UNITLESS_MQ_ALWAYS_FIRST && (!minA && !maxA || !minB && !maxB)) {
118
+ if (!minA && !maxA && !minB && !maxB) {
119
+ return a.localeCompare(b);
120
+ }
121
+ return !minB && !maxB ? 1 : -1;
122
+ } else {
123
+ if (minA && maxB) {
124
+ return -1;
125
+ }
126
+ if (maxA && minB) {
127
+ return 1;
128
+ }
129
+ const lengthA = _getQueryLength(a);
130
+ const lengthB = _getQueryLength(b);
131
+ if (lengthA === maxValue && lengthB === maxValue) {
132
+ return a.localeCompare(b);
133
+ } else if (lengthA === maxValue) {
134
+ return 1;
135
+ } else if (lengthB === maxValue) {
136
+ return -1;
137
+ }
138
+ if (lengthA > lengthB) {
139
+ if (maxA) {
140
+ return -1;
141
+ }
142
+ return 1;
143
+ }
144
+ if (lengthA < lengthB) {
145
+ if (maxA) {
146
+ return 1;
147
+ }
148
+ return -1;
149
+ }
150
+ if (lengthA === lengthB) {
151
+ if (maxA && maxB) {
152
+ if (lessThan.test(a) && !lessThan.test(b)) {
153
+ return 1;
154
+ }
155
+ if (!lessThan.test(a) && lessThan.test(b)) {
156
+ return -1;
157
+ }
158
+ }
159
+ if (minA && minB) {
160
+ if (grtrThan.test(a) && !grtrThan.test(b)) {
161
+ return 1;
162
+ }
163
+ if (!grtrThan.test(a) && grtrThan.test(b)) {
164
+ return -1;
165
+ }
166
+ }
167
+ }
168
+ return a.localeCompare(b);
169
+ }
170
+ }
171
+ sortCSSmq.desktopFirst = function(a, b) {
172
+ const testIsPrint = _testIsPrint(a, b);
173
+ if (testIsPrint !== null) {
174
+ return testIsPrint;
175
+ }
176
+ const minA = isMinWidth(a) || isMinHeight(a);
177
+ const maxA = isMaxWidth(a) || isMaxHeight(a);
178
+ const minB = isMinWidth(b) || isMinHeight(b);
179
+ const maxB = isMaxWidth(b) || isMaxHeight(b);
180
+ if (UNITLESS_MQ_ALWAYS_FIRST && (!minA && !maxA || !minB && !maxB)) {
181
+ if (!minA && !maxA && !minB && !maxB) {
182
+ return a.localeCompare(b);
183
+ }
184
+ return !minB && !maxB ? 1 : -1;
185
+ } else {
186
+ if (minA && maxB) {
187
+ return 1;
188
+ }
189
+ if (maxA && minB) {
190
+ return -1;
191
+ }
192
+ const lengthA = _getQueryLength(a);
193
+ const lengthB = _getQueryLength(b);
194
+ if (lengthA === maxValue && lengthB === maxValue) {
195
+ return a.localeCompare(b);
196
+ } else if (lengthA === maxValue) {
197
+ return 1;
198
+ } else if (lengthB === maxValue) {
199
+ return -1;
200
+ }
201
+ if (lengthA > lengthB) {
202
+ if (maxA) {
203
+ return -1;
204
+ }
205
+ return 1;
206
+ }
207
+ if (lengthA < lengthB) {
208
+ if (maxA) {
209
+ return 1;
210
+ }
211
+ return -1;
212
+ }
213
+ if (lengthA === lengthB) {
214
+ if (maxA && maxB) {
215
+ if (lessThan.test(a) && !lessThan.test(b)) {
216
+ return 1;
217
+ }
218
+ if (!lessThan.test(a) && lessThan.test(b)) {
219
+ return -1;
220
+ }
221
+ }
222
+ if (minA && minB) {
223
+ if (grtrThan.test(a) && !grtrThan.test(b)) {
224
+ return 1;
225
+ }
226
+ if (!grtrThan.test(a) && grtrThan.test(b)) {
227
+ return -1;
228
+ }
229
+ }
230
+ }
231
+ return -a.localeCompare(b);
232
+ }
233
+ };
234
+ return sortCSSmq;
235
+ }
236
+
237
+ // src/index.js
238
+ function sortAtRules(queries, options, sortCSSmq) {
239
+ if (typeof options.sort !== "function") {
240
+ options.sort = options.sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
241
+ }
242
+ return queries.sort(options.sort);
243
+ }
244
+ function plugin(options = {}) {
245
+ options = Object.assign(
246
+ {
247
+ sort: "mobile-first",
248
+ configuration: false
249
+ },
250
+ options
251
+ );
252
+ const sortCSSmq = createSort(options.configuration);
253
+ return {
254
+ postcssPlugin: "postcss-sort-media-queries",
255
+ // Execute once after the entire tree has been parsed
256
+ OnceExit(root, { AtRule }) {
257
+ let parents = {
258
+ root: [],
259
+ nested: []
260
+ };
261
+ let processed = /* @__PURE__ */ Symbol("processed");
262
+ 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);
271
+ }
272
+ atRule.parent[processed] = true;
273
+ return;
274
+ });
275
+ Object.keys(parents).forEach((type) => {
276
+ if (!parents[type].length) {
277
+ return;
278
+ }
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]);
304
+ });
305
+ }
306
+ });
307
+ });
308
+ }
309
+ };
310
+ }
311
+ plugin.postcss = true;
312
+ var index_default = plugin;
@@ -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,7 +1,10 @@
1
1
  {
2
2
  "name": "postcss-sort-media-queries",
3
- "version": "5.2.0",
4
3
  "description": "PostCSS plugin for sorting and combining CSS media queries with mobile first / **desktop first methodologies",
4
+ "version": "6.1.0",
5
+ "engines": {
6
+ "node": ">=22.0.0"
7
+ },
5
8
  "keywords": [
6
9
  "postcss",
7
10
  "postcss-plugin",
@@ -29,67 +32,37 @@
29
32
  "url": "https://github.com/yunusga/postcss-sort-media-queries/issues"
30
33
  },
31
34
  "homepage": "https://github.com/yunusga/postcss-sort-media-queries",
32
- "scripts": {
33
- "test": "jest-ci --coverage && eslint",
34
- "refresh-deps": "rm -rf node_modules && rm package-lock.json && npm i"
35
- },
36
- "engines": {
37
- "node": ">=14.0.0"
35
+ "type": "module",
36
+ "main": "src/index.js",
37
+ "module": "src/index.js",
38
+ "exports": {
39
+ ".": {
40
+ "require": "./build/wrapper.cjs",
41
+ "import": "./src/index.js",
42
+ "default": "./src/index.js"
43
+ }
38
44
  },
39
- "dependencies": {
40
- "sort-css-media-queries": "2.2.0"
45
+ "files": [
46
+ "build"
47
+ ],
48
+ "scripts": {
49
+ "test": "vitest run",
50
+ "test:watch": "vitest --watch",
51
+ "coverage": "vitest --coverage",
52
+ "build": "npm run build:cjs",
53
+ "build:cjs": "esbuild src/index.js --outfile=build/index.cjs --format=cjs --bundle --platform=node --external:postcss"
41
54
  },
42
55
  "devDependencies": {
43
- "autoprefixer": "^10.4.0",
44
- "eslint": "^8.3.0",
45
- "eslint-ci": "^1.0.0",
46
- "eslint-plugin-jest": "^25.3.0",
47
- "husky": "^7.0.4",
48
- "jest": "^27.3.1",
49
- "jest-ci": "^0.1.1",
50
- "jest-cli": "^27.3.1",
51
- "lint-staged": "^13.2.1",
52
- "postcss": "^8.4.23",
53
- "postcss-flexbugs-fixes": "^5.0.2",
54
- "postcss-media-minmax": "^5.0.0",
55
- "postcss-nested": "^5.0.6"
56
+ "@vitest/coverage-v8": "^4.0.18",
57
+ "esbuild": "^0.27.3",
58
+ "postcss": "^8.5.6",
59
+ "prettier": "3.8.1",
60
+ "vitest": "^4.0.18"
56
61
  },
57
62
  "peerDependencies": {
58
- "postcss": "^8.4.23"
63
+ "postcss": "^8.5.6"
59
64
  },
60
- "husky": {
61
- "hooks": {
62
- "pre-commit": "lint-staged"
63
- }
64
- },
65
- "lint-staged": {
66
- "*.js": "eslint --fix"
67
- },
68
- "eslintConfig": {
69
- "parserOptions": {
70
- "ecmaVersion": 2017
71
- },
72
- "env": {
73
- "node": true,
74
- "es6": true
75
- },
76
- "extends": [
77
- "eslint:recommended",
78
- "plugin:jest/recommended"
79
- ],
80
- "rules": {
81
- "jest/expect-expect": "off"
82
- }
83
- },
84
- "jest": {
85
- "testEnvironment": "node",
86
- "coverageThreshold": {
87
- "global": {
88
- "statements": 100
89
- }
90
- }
91
- },
92
- "sortCssMQ": {
93
- "unitlessMqAlwaysFirst": false
65
+ "dependencies": {
66
+ "sort-css-media-queries": "^3.0.1"
94
67
  }
95
68
  }
package/src/index.js ADDED
@@ -0,0 +1,122 @@
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;
package/browser.js DELETED
@@ -1,54 +0,0 @@
1
- function sortAtRules(queries, sort, sortCSSmq) {
2
- if (typeof sort !== 'function') {
3
- sort = sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq
4
- }
5
-
6
- return queries.sort(sort)
7
- }
8
-
9
- module.exports = (opts = {}) => {
10
-
11
- opts = Object.assign(
12
- {
13
- sort: 'mobile-first',
14
- configuration: {
15
- unitlessMqAlwaysFirst: false,
16
- },
17
- },
18
- opts
19
- )
20
-
21
- const createSort = require('sort-css-media-queries/lib/create-sort');
22
- const sortCSSmq = createSort(opts.configuration);
23
-
24
- return {
25
- postcssPlugin: 'postcss-sort-media-queries',
26
- OnceExit(root, { AtRule }) {
27
-
28
- let atRules = [];
29
-
30
- root.walkAtRules('media', atRule => {
31
- let query = atRule.params
32
-
33
- if (!atRules[query]) {
34
- atRules[query] = new AtRule({
35
- name: atRule.name,
36
- params: atRule.params,
37
- source: atRule.source
38
- })
39
- }
40
-
41
- atRule.nodes.forEach(node => {
42
- atRules[query].append(node.clone())
43
- })
44
-
45
- atRule.remove()
46
- })
47
-
48
- sortAtRules(Object.keys(atRules), opts.sort, sortCSSmq).forEach(query => {
49
- root.append(atRules[query])
50
- })
51
- }
52
- }
53
- }
54
- module.exports.postcss = true
package/index.js DELETED
@@ -1,75 +0,0 @@
1
- function sortAtRules(queries, sort, sortCSSmq) {
2
- if (typeof sort !== 'function') {
3
- sort = sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq
4
- }
5
-
6
- return queries.sort(sort)
7
- }
8
-
9
- module.exports = (opts = {}) => {
10
-
11
- opts = Object.assign(
12
- {
13
- sort: 'mobile-first',
14
- configuration: false,
15
- onlyTopLevel: false,
16
- },
17
- opts
18
- )
19
-
20
- const createSort = require('sort-css-media-queries/lib/create-sort');
21
- const sortCSSmq = opts.configuration ? createSort(opts.configuration) : require('sort-css-media-queries');
22
-
23
- return {
24
- postcssPlugin: 'postcss-sort-media-queries',
25
- OnceExit (root, { AtRule }) {
26
-
27
- let atRules = [];
28
-
29
- root.walkAtRules('media', atRule => {
30
- if (opts.onlyTopLevel && atRule.parent.type === 'root') {
31
- let query = atRule.params
32
-
33
- if (!atRules[query]) {
34
- atRules[query] = new AtRule({
35
- name: atRule.name,
36
- params: atRule.params,
37
- source: atRule.source
38
- })
39
- }
40
-
41
- atRule.nodes.forEach(node => {
42
- atRules[query].append(node.clone())
43
- })
44
-
45
- atRule.remove()
46
- }
47
-
48
- if (!opts.onlyTopLevel) {
49
- let query = atRule.params
50
-
51
- if (!atRules[query]) {
52
- atRules[query] = new AtRule({
53
- name: atRule.name,
54
- params: atRule.params,
55
- source: atRule.source
56
- })
57
- }
58
-
59
- atRule.nodes.forEach(node => {
60
- atRules[query].append(node.clone())
61
- })
62
-
63
- atRule.remove()
64
- }
65
- })
66
-
67
- if (atRules) {
68
- sortAtRules(Object.keys(atRules), opts.sort, sortCSSmq).forEach(query => {
69
- root.append(atRules[query])
70
- })
71
- }
72
- }
73
- }
74
- }
75
- module.exports.postcss = true