1 | 'use strict';
|
2 | const valueParser = require('postcss-value-parser');
|
3 | const browserslist = require('browserslist');
|
4 | const convert = require('./lib/convert.js');
|
5 |
|
6 | const LENGTH_UNITS = new Set([
|
7 | 'em',
|
8 | 'ex',
|
9 | 'ch',
|
10 | 'rem',
|
11 | 'vw',
|
12 | 'vh',
|
13 | 'vmin',
|
14 | 'vmax',
|
15 | 'cm',
|
16 | 'mm',
|
17 | 'q',
|
18 | 'in',
|
19 | 'pt',
|
20 | 'pc',
|
21 | 'px',
|
22 | ]);
|
23 |
|
24 |
|
25 | const notALength = new Set([
|
26 | 'descent-override',
|
27 | 'ascent-override',
|
28 | 'font-stretch',
|
29 | 'size-adjust',
|
30 | 'line-gap-override',
|
31 | ]);
|
32 |
|
33 |
|
34 | const keepWhenZero = new Set([
|
35 | 'stroke-dashoffset',
|
36 | 'stroke-width',
|
37 | 'line-height',
|
38 | ]);
|
39 |
|
40 |
|
41 | const keepZeroPercent = new Set(['max-height', 'height', 'min-width']);
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function stripLeadingDot(item) {
|
52 | if (item.charCodeAt(0) === '.'.charCodeAt(0)) {
|
53 | return item.slice(1);
|
54 | } else {
|
55 | return item;
|
56 | }
|
57 | }
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | function parseWord(node, opts, keepZeroUnit) {
|
66 | const pair = valueParser.unit(node.value);
|
67 | if (pair) {
|
68 | const num = Number(pair.number);
|
69 | const u = stripLeadingDot(pair.unit);
|
70 | if (num === 0) {
|
71 | node.value =
|
72 | 0 +
|
73 | (keepZeroUnit || (!LENGTH_UNITS.has(u.toLowerCase()) && u !== '%')
|
74 | ? u
|
75 | : '');
|
76 | } else {
|
77 | node.value = convert(num, u, opts);
|
78 |
|
79 | if (
|
80 | typeof opts.precision === 'number' &&
|
81 | u.toLowerCase() === 'px' &&
|
82 | pair.number.includes('.')
|
83 | ) {
|
84 | const precision = Math.pow(10, opts.precision);
|
85 | node.value =
|
86 | Math.round(parseFloat(node.value) * precision) / precision + u;
|
87 | }
|
88 | }
|
89 | }
|
90 | }
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | function clampOpacity(node) {
|
97 | const pair = valueParser.unit(node.value);
|
98 | if (!pair) {
|
99 | return;
|
100 | }
|
101 | let num = Number(pair.number);
|
102 | if (num > 1) {
|
103 | node.value = pair.unit === '%' ? num + pair.unit : 1 + pair.unit;
|
104 | } else if (num < 0) {
|
105 | node.value = 0 + pair.unit;
|
106 | }
|
107 | }
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | function shouldKeepZeroUnit(decl, browsers) {
|
115 | const { parent } = decl;
|
116 | const lowerCasedProp = decl.prop.toLowerCase();
|
117 | return (
|
118 | (decl.value.includes('%') &&
|
119 | keepZeroPercent.has(lowerCasedProp) &&
|
120 | browsers.includes('ie 11')) ||
|
121 | (parent &&
|
122 | parent.parent &&
|
123 | parent.parent.type === 'atrule' &&
|
124 | (
|
125 | parent.parent
|
126 | ).name.toLowerCase() === 'keyframes' &&
|
127 | lowerCasedProp === 'stroke-dasharray') ||
|
128 | keepWhenZero.has(lowerCasedProp)
|
129 | );
|
130 | }
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 | function transform(opts, browsers, decl) {
|
138 | const lowerCasedProp = decl.prop.toLowerCase();
|
139 | if (
|
140 | lowerCasedProp.includes('flex') ||
|
141 | lowerCasedProp.indexOf('--') === 0 ||
|
142 | notALength.has(lowerCasedProp)
|
143 | ) {
|
144 | return;
|
145 | }
|
146 |
|
147 | decl.value = valueParser(decl.value)
|
148 | .walk((node) => {
|
149 | const lowerCasedValue = node.value.toLowerCase();
|
150 |
|
151 | if (node.type === 'word') {
|
152 | parseWord(node, opts, shouldKeepZeroUnit(decl, browsers));
|
153 | if (
|
154 | lowerCasedProp === 'opacity' ||
|
155 | lowerCasedProp === 'shape-image-threshold'
|
156 | ) {
|
157 | clampOpacity(node);
|
158 | }
|
159 | } else if (node.type === 'function') {
|
160 | if (
|
161 | lowerCasedValue === 'calc' ||
|
162 | lowerCasedValue === 'min' ||
|
163 | lowerCasedValue === 'max' ||
|
164 | lowerCasedValue === 'clamp' ||
|
165 | lowerCasedValue === 'hsl' ||
|
166 | lowerCasedValue === 'hsla'
|
167 | ) {
|
168 | valueParser.walk(node.nodes, (n) => {
|
169 | if (n.type === 'word') {
|
170 | parseWord(n, opts, true);
|
171 | }
|
172 | });
|
173 | return false;
|
174 | }
|
175 | if (lowerCasedValue === 'url') {
|
176 | return false;
|
177 | }
|
178 | }
|
179 | })
|
180 | .toString();
|
181 | }
|
182 |
|
183 | const plugin = 'postcss-convert-values';
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | function pluginCreator(opts = { precision: false }) {
|
192 | const browsers = browserslist(null, {
|
193 | stats: opts.stats,
|
194 | path: __dirname,
|
195 | env: opts.env,
|
196 | });
|
197 |
|
198 | return {
|
199 | postcssPlugin: plugin,
|
200 | OnceExit(css) {
|
201 | css.walkDecls((decl) => transform(opts, browsers, decl));
|
202 | },
|
203 | };
|
204 | }
|
205 |
|
206 | pluginCreator.postcss = true;
|
207 | module.exports = pluginCreator;
|