postcss-custom-supports 0.1.3 → 1.0.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 +64 -0
- package/index.js +75 -34
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ npm install --save-dev postcss-custom-supports
|
|
|
54
54
|
|
|
55
55
|
## Usage
|
|
56
56
|
|
|
57
|
+
**CommonJS**
|
|
58
|
+
|
|
57
59
|
```js
|
|
58
60
|
const postcss = require('postcss');
|
|
59
61
|
const customSupports = require('postcss-custom-supports');
|
|
@@ -61,6 +63,66 @@ const customSupports = require('postcss-custom-supports');
|
|
|
61
63
|
postcss([customSupports(/* options */)]).process(css);
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
**Vite** — add to `css.postcss` in `vite.config.js` / `vite.config.ts`:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { defineConfig } from 'vite';
|
|
70
|
+
import customSupports from 'postcss-custom-supports';
|
|
71
|
+
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
css: {
|
|
74
|
+
postcss: {
|
|
75
|
+
plugins: [customSupports(/* options */)],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Tailwind CSS v3** — add to `postcss.config.mjs` alongside your other PostCSS plugins:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import tailwindcss from 'tailwindcss';
|
|
85
|
+
import customSupports from 'postcss-custom-supports';
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
plugins: [
|
|
89
|
+
tailwindcss,
|
|
90
|
+
customSupports(/* options */),
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Tailwind CSS v4 + Vite** — Tailwind ships as a Vite plugin in v4; add this plugin to `css.postcss` in `vite.config.js`:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { defineConfig } from 'vite';
|
|
99
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
100
|
+
import customSupports from 'postcss-custom-supports';
|
|
101
|
+
|
|
102
|
+
export default defineConfig({
|
|
103
|
+
plugins: [tailwindcss()],
|
|
104
|
+
css: {
|
|
105
|
+
postcss: {
|
|
106
|
+
plugins: [customSupports(/* options */)],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Tailwind CSS v4 + PostCSS** (non-Vite) — use `@tailwindcss/postcss` in `postcss.config.mjs`:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
import tailwindcss from '@tailwindcss/postcss';
|
|
116
|
+
import customSupports from 'postcss-custom-supports';
|
|
117
|
+
|
|
118
|
+
export default {
|
|
119
|
+
plugins: [
|
|
120
|
+
tailwindcss,
|
|
121
|
+
customSupports(/* options */),
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
64
126
|
### Options
|
|
65
127
|
|
|
66
128
|
| Option | Type | Default | Description |
|
|
@@ -87,6 +149,8 @@ A reference is the literal token `(--<name>)`, used anywhere a parenthesized sup
|
|
|
87
149
|
|
|
88
150
|
References inside function calls (`var(--name)`, `attr(--name)`, etc.) are **not** rewritten, so it is safe to reference custom properties in supports conditions.
|
|
89
151
|
|
|
152
|
+
_Note: When we call a custom supports token, we wrap it in parentheses. This follows the structure of postcss-custom-media, but it also gives an easier visual indicator of the token, compared to without parentheses. It’s a taste thing, but it made sense to me._
|
|
153
|
+
|
|
90
154
|
## Warnings
|
|
91
155
|
|
|
92
156
|
The plugin emits PostCSS warnings (not errors) for:
|
package/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const valueParser = require('postcss-value-parser');
|
|
4
|
+
|
|
3
5
|
// Matches a single @custom-supports declaration:
|
|
4
6
|
// @custom-supports --name <condition>;
|
|
5
7
|
// PostCSS strips the trailing semicolon and normalizes whitespace before
|
|
@@ -7,47 +9,86 @@
|
|
|
7
9
|
// is all we need to require here.
|
|
8
10
|
const DEFINITION_RE = /^(--[\w-]+)\s+(.+)$/;
|
|
9
11
|
|
|
10
|
-
// Matches a (--name) reference inside an @supports condition. The negative
|
|
11
|
-
// lookbehind prevents accidental rewrites of identifiers like var(--foo) or
|
|
12
|
-
// attr(--bar), where the leading character before "(" is an ident-char.
|
|
13
|
-
const REFERENCE_RE = /(?<![\w-])\(--[\w-]+\)/g;
|
|
14
|
-
|
|
15
12
|
const creator = (opts = {}) => {
|
|
16
13
|
const preserve = opts.preserve === true;
|
|
17
14
|
|
|
18
15
|
return {
|
|
19
16
|
postcssPlugin: 'postcss-custom-supports',
|
|
20
|
-
|
|
17
|
+
// prepare() gives us per-process state isolation: a fresh Map per file.
|
|
18
|
+
// Collection runs as a typed AtRule visitor (joins the main walk for free),
|
|
19
|
+
// and replacement defers to OnceExit so all definitions — including ones
|
|
20
|
+
// declared after their first reference — are resolved before rewriting.
|
|
21
|
+
prepare() {
|
|
21
22
|
const customs = new Map();
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
return {
|
|
25
|
+
AtRule: {
|
|
26
|
+
'custom-supports'(rule, { result }) {
|
|
27
|
+
const match = rule.params.match(DEFINITION_RE);
|
|
28
|
+
if (!match) {
|
|
29
|
+
rule.warn(result, `Invalid @custom-supports declaration: "${rule.params}"`);
|
|
30
|
+
if (!preserve) rule.remove();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const [, name, condition] = match;
|
|
35
|
+
if (customs.has(name)) {
|
|
36
|
+
rule.warn(result, `@custom-supports ${name} is redefined; the last definition wins`);
|
|
37
|
+
}
|
|
38
|
+
customs.set(name, condition.trim());
|
|
39
|
+
|
|
40
|
+
if (!preserve) rule.remove();
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
OnceExit(root, { result }) {
|
|
45
|
+
// Recurse bottom-up so that inner @supports rules are rewritten
|
|
46
|
+
// before the outer rule is cloned. If we used root.walkAtRules()
|
|
47
|
+
// instead, replaceWith(clone) would cause the walker to descend into
|
|
48
|
+
// the detached original rather than the clone, leaving inner
|
|
49
|
+
// references unexpanded.
|
|
50
|
+
const processContainer = container => {
|
|
51
|
+
container.each(node => {
|
|
52
|
+
if (node.nodes) processContainer(node);
|
|
53
|
+
if (node.type !== 'atrule' || node.name !== 'supports') return;
|
|
54
|
+
|
|
55
|
+
const parsed = valueParser(node.params);
|
|
56
|
+
let changed = false;
|
|
57
|
+
|
|
58
|
+
parsed.walk(valueNode => {
|
|
59
|
+
// value-parser gives bare parentheses type 'function' with an
|
|
60
|
+
// empty value string, which naturally excludes named functions
|
|
61
|
+
// like var(--x) or attr(--x) — cleaner than the lookbehind
|
|
62
|
+
// regex this replaces.
|
|
63
|
+
if (valueNode.type !== 'function' || valueNode.value !== '') return;
|
|
64
|
+
if (valueNode.nodes.length !== 1 || valueNode.nodes[0].type !== 'word') return;
|
|
65
|
+
const name = valueNode.nodes[0].value;
|
|
66
|
+
if (!/^--[\w-]+$/.test(name)) return;
|
|
67
|
+
|
|
68
|
+
const condition = customs.get(name);
|
|
69
|
+
if (condition === undefined) {
|
|
70
|
+
node.warn(result, `Unknown @custom-supports reference: ${name}`);
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Expand the condition into this paren node; stop descending
|
|
75
|
+
// so we never re-process the freshly substituted nodes.
|
|
76
|
+
valueNode.nodes = valueParser(condition).nodes;
|
|
77
|
+
changed = true;
|
|
78
|
+
return false;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (changed) {
|
|
82
|
+
// .clone() preserves the original node's source position so
|
|
83
|
+
// PostCSS can emit accurate source maps for the rewritten rule.
|
|
84
|
+
node.replaceWith(node.clone({ params: valueParser.stringify(parsed) }));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
processContainer(root);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
51
92
|
},
|
|
52
93
|
};
|
|
53
94
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-custom-supports",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "PostCSS plugin that mirrors @custom-media for @supports queries, letting you alias verbose or experimental feature-detection conditions behind a custom name.",
|
|
5
5
|
"author": "Chloe Burroughs <chloe@pfr.wtf>",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"index.js"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "node --test index.test.js"
|
|
19
|
+
"test": "c8 node --test index.test.js"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"postcss",
|
|
@@ -24,13 +24,21 @@
|
|
|
24
24
|
"css",
|
|
25
25
|
"supports",
|
|
26
26
|
"custom-supports",
|
|
27
|
-
"feature-queries"
|
|
27
|
+
"feature-queries",
|
|
28
|
+
"atRules"
|
|
28
29
|
],
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"postcss": "^8.4.0"
|
|
31
32
|
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"c8": "^11.0.0",
|
|
35
|
+
"postcss": "^8.4.0"
|
|
36
|
+
},
|
|
32
37
|
"engines": {
|
|
33
38
|
"node": ">=18.0.0"
|
|
34
39
|
},
|
|
35
|
-
"license": "MIT"
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"postcss-value-parser": "^4.2.0"
|
|
43
|
+
}
|
|
36
44
|
}
|