@forumone/postcss-advanced-variables 0.0.0-stage → 5.0.1

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.
Files changed (5) hide show
  1. package/LICENSE.md +108 -0
  2. package/README.md +575 -2
  3. package/index.js +730 -0
  4. package/index.mjs +728 -0
  5. package/package.json +55 -4
package/index.mjs ADDED
@@ -0,0 +1,728 @@
1
+ import postcss, { list } from 'postcss';
2
+ import path from 'path';
3
+ import resolve from '@csstools/sass-import-resolve';
4
+
5
+ // return the closest variable from a node
6
+ function getClosestVariable(name, node, opts) {
7
+ const variables = getVariables(node);
8
+
9
+ let variable = variables[name];
10
+
11
+ if (requiresAncestorVariable(variable, node)) {
12
+ variable = getClosestVariable(name, node.parent, opts);
13
+ }
14
+
15
+ if (requiresFnVariable(variable, opts)) {
16
+ variable = getFnVariable(name, node, opts.variables);
17
+ }
18
+
19
+ return variable;
20
+ }
21
+
22
+ // return the variables object of a node
23
+ const getVariables = node => Object(Object(node).variables);
24
+
25
+ // return whether the variable should be replaced using an ancestor variable
26
+ const requiresAncestorVariable = (variable, node) => undefined === variable && node && node.parent;
27
+
28
+ // return whether variable should be replaced using a variables function
29
+ const requiresFnVariable = (value, opts) => value === undefined && Object(opts).variables === Object(Object(opts).variables);
30
+
31
+ // return whether variable should be replaced using a variables function
32
+ const getFnVariable = (name, node, variables) => 'function' === typeof variables
33
+ ? variables(name, node)
34
+ : variables[name];
35
+
36
+ function manageUnresolved(node, opts, word, message) {
37
+ if ('warn' === opts.unresolved) {
38
+ node.warn(opts.result, message, { word });
39
+ } else if ('ignore' !== opts.unresolved) {
40
+ throw node.error(message, { word });
41
+ }
42
+ }
43
+
44
+ // tooling
45
+
46
+ // return content with its variables replaced by the corresponding values of a node
47
+ function getReplacedString(string, node, opts) {
48
+ const replacedString = string.replace(
49
+ matchVariables,
50
+ (match, before, name1, name2, name3) => {
51
+ // conditionally return an (unescaped) match
52
+ if (before === '\\') {
53
+ return match.slice(1);
54
+ }
55
+
56
+ // the first matching variable name
57
+ const name = name1 || name2 || name3;
58
+
59
+ // the closest variable value
60
+ const value = getClosestVariable(name, node.parent, opts);
61
+
62
+ // if a variable has not been resolved
63
+ if (undefined === value) {
64
+ manageUnresolved(node, opts, name, `Could not resolve the variable "$${name}" within "${string}"`);
65
+
66
+ return match;
67
+ }
68
+
69
+ // the stringified value
70
+ const stringifiedValue = `${before}${stringify(value)}`;
71
+
72
+ return stringifiedValue;
73
+ }
74
+ );
75
+
76
+ return replacedString;
77
+ }
78
+
79
+ // match all $name, $(name), and #{$name} variables (and catch the character before it)
80
+ const matchVariables = /(.?)(?:\$([a-zA-Z][\w-]*)|\$\(([a-zA-Z][\w-]*)\)|#\{\$([a-zA-Z][\w-]*)\})/g;
81
+
82
+ // return a sass stringified variable
83
+ const stringify = object => Array.isArray(object)
84
+ ? `(${object.map(stringify).join(',')})`
85
+ : Object(object) === object
86
+ ? `(${Object.keys(object).map(
87
+ key => `${key}:${stringify(object[key])}`
88
+ ).join(',')})`
89
+ : String(object);
90
+
91
+ // tooling
92
+
93
+ // set a variable on a node
94
+ function setVariable(node, name, value, opts) {
95
+ // if the value is not a default with a value already defined
96
+ if (!matchDefault.test(value) || getClosestVariable(name, node, opts) === undefined) {
97
+ // the value without a default suffix
98
+ const undefaultedValue = matchDefault.test(value)
99
+ ? value.replace(matchDefault, '')
100
+ : value;
101
+
102
+ // ensure the node has a variables object
103
+ node.variables = node.variables || {};
104
+
105
+ // set the variable
106
+ node.variables[name] = undefaultedValue;
107
+ }
108
+ }
109
+
110
+ // match anything ending with a valid !default
111
+ const matchDefault = /\s+!default$/;
112
+
113
+ // tooling
114
+
115
+ // transform declarations
116
+ function transformDecl(decl, opts) {
117
+ // update the declaration value with its variables replaced by their corresponding values
118
+ decl.value = getReplacedString(decl.value, decl, opts);
119
+
120
+
121
+ // if the declaration is a variable declaration
122
+ if (isVariableDeclaration(decl)) {
123
+ // set the variable on the parent of the declaration
124
+ setVariable(decl.parent, decl.prop.slice(1), decl.value, opts);
125
+
126
+ // remove the declaration
127
+ decl.remove();
128
+ } else {
129
+ decl.prop = getReplacedString(decl.prop, decl, opts);
130
+ }
131
+ }
132
+
133
+ // return whether the declaration property is a variable declaration
134
+ const isVariableDeclaration = decl => matchVariable.test(decl.prop);
135
+
136
+ // match a variable ($any-name)
137
+ const matchVariable = /^\$[\w-]+$/;
138
+
139
+ // tooling
140
+
141
+ // transform generic at-rules
142
+ function transformAtrule(rule, opts) {
143
+ // update the at-rule params with its variables replaced by their corresponding values
144
+ rule.params = getReplacedString(rule.params, rule, opts);
145
+ }
146
+
147
+ // tooling
148
+
149
+ // return sass-like arrays as literal arrays ('(hello), (goodbye)' to [[hello], [goodbye]])
150
+ function getValueAsObject(value) {
151
+ const hasWrappingParens = matchWrappingParens.test(value);
152
+ const unwrappedValue = String(hasWrappingParens ? value.replace(matchWrappingParens, '$1') : value).replace(matchTrailingComma, '');
153
+ const separatedValue = list.comma(unwrappedValue);
154
+ const firstValue = separatedValue[0];
155
+
156
+ if (firstValue === value) {
157
+ return value;
158
+ } else {
159
+ const objectValue = {};
160
+ const arrayValue = [];
161
+
162
+ separatedValue.forEach(
163
+ (subvalue, index) => {
164
+ const [ match, key, keyvalue ] = subvalue.match(matchDeclaration) || [];
165
+
166
+ if (match) {
167
+ objectValue[key] = getValueAsObject(keyvalue);
168
+ } else {
169
+ arrayValue[index] = getValueAsObject(subvalue);
170
+ }
171
+ }
172
+ );
173
+
174
+ const transformedValue = Object.keys(objectValue).length > 0
175
+ ? Object.assign(objectValue, arrayValue)
176
+ : arrayValue;
177
+
178
+ return transformedValue;
179
+ }
180
+ }
181
+
182
+ // match wrapping parentheses ((), (anything), (anything (anything)))
183
+ const matchWrappingParens = /^\(([\W\w]*)\)$/g;
184
+
185
+ // match a property name (any-possible_name)
186
+ const matchDeclaration = /^([\w-]+)\s*:\s*([\W\w]+)\s*$/;
187
+
188
+ // match a trailing comma
189
+ const matchTrailingComma = /\s*,\s*$/;
190
+
191
+ var waterfall = (items, asyncFunction) => items.reduce(
192
+ (lastPromise, item) => lastPromise.then(
193
+ () => asyncFunction(item)
194
+ ),
195
+ Promise.resolve()
196
+ );
197
+
198
+ // tooling
199
+
200
+ // transform @each at-rules
201
+ function transformEachAtrule(rule, opts) {
202
+ // if @each is supported
203
+ if (opts.transform.includes('@each')) {
204
+ // @each options
205
+ const { varname, incname, list } = getEachOpts(rule, opts);
206
+ const replacements = [];
207
+ const ruleClones = [];
208
+
209
+ Object.keys(list).forEach(
210
+ key => {
211
+ // set the current variable
212
+ setVariable(rule, varname, list[key], opts);
213
+
214
+ // conditionally set the incremenator variable
215
+ if (incname) {
216
+ setVariable(rule, incname, key, opts);
217
+ }
218
+
219
+ // clone the @each at-rule
220
+ const clone = rule.clone({
221
+ parent: rule.parent,
222
+ variables: Object.assign({}, rule.variables)
223
+ });
224
+
225
+ ruleClones.push(clone);
226
+ }
227
+ );
228
+
229
+ return waterfall(
230
+ ruleClones,
231
+ clone => transformNode(clone, opts).then(
232
+ () => {
233
+ replacements.push(...clone.nodes);
234
+ }
235
+ )
236
+ ).then(
237
+ () => {
238
+ // replace the @each at-rule with the replacements
239
+ rule.parent.insertBefore(rule, replacements);
240
+
241
+ rule.remove();
242
+ }
243
+ )
244
+ }
245
+ }
246
+
247
+ // return the @each statement options (@each NAME in LIST, @each NAME ITERATOR in LIST)
248
+ const getEachOpts = (node, opts) => {
249
+ const params = node.params.split(matchInOperator);
250
+ const args = (params[0] || '').trim().split(' ');
251
+ const varname = args[0].trim().slice(1);
252
+ const incname = args.length > 1 && args[1].trim().slice(1);
253
+ const rawlist = getValueAsObject(
254
+ getReplacedString(
255
+ params.slice(1).join(matchInOperator),
256
+ node,
257
+ opts
258
+ )
259
+ );
260
+ const list = 'string' === typeof rawlist ? [rawlist] : rawlist;
261
+
262
+ return { varname, incname, list };
263
+ };
264
+
265
+ // match the opertor separating the name and iterator from the list
266
+ const matchInOperator = ' in ';
267
+
268
+ // tooling
269
+
270
+ // transform @if at-rules
271
+ function transformIfAtrule(rule, opts) {
272
+ // @if options
273
+ const isTruthy = isIfTruthy(rule, opts);
274
+ const next = rule.next();
275
+
276
+ const transformAndInsertBeforeParent = node => transformNode(node, opts).then(
277
+ () => node.parent.insertBefore(node, node.nodes)
278
+ );
279
+
280
+ return ifPromise(
281
+ opts.transform.includes('@if'),
282
+ () => ifPromise(
283
+ isTruthy,
284
+ () => transformAndInsertBeforeParent(rule)
285
+ ).then(() => {
286
+ rule.remove();
287
+ })
288
+ ).then(() => ifPromise(
289
+ opts.transform.includes('@else') && isElseRule(next),
290
+ () => ifPromise(
291
+ !isTruthy,
292
+ () => transformAndInsertBeforeParent(next)
293
+ ).then(() => {
294
+ next.remove();
295
+ })
296
+ ))
297
+ }
298
+
299
+ const ifPromise = (condition, trueFunction) => Promise.resolve(condition && trueFunction());
300
+
301
+ // return whether the @if at-rule is truthy
302
+ const isIfTruthy = (node, opts) => {
303
+ // @if statement options (@if EXPRESSION, @if LEFT OPERATOR RIGHT)
304
+ const params = list.space(node.params);
305
+ const left = getInterprettedString(getReplacedString(params[0] || '', node, opts));
306
+ const operator = params[1];
307
+ const right = getInterprettedString(getReplacedString(params[2] || '', node, opts));
308
+
309
+ // evaluate the expression
310
+ const isTruthy = !operator && left ||
311
+ operator === '==' && left === right ||
312
+ operator === '!=' && left !== right ||
313
+ operator === '<' && left < right ||
314
+ operator === '<=' && left <= right ||
315
+ operator === '>' && left > right ||
316
+ operator === '>=' && left >= right;
317
+
318
+ return isTruthy;
319
+ };
320
+
321
+ // return the value as a boolean, number, or string
322
+ const getInterprettedString = value => 'true' === value
323
+ ? true
324
+ : 'false' === value
325
+ ? false
326
+ : isNaN(value)
327
+ ? value
328
+ : Number(value);
329
+
330
+ // return whether the node is an else at-rule
331
+ const isElseRule = node => Object(node) === node && 'atrule' === node.type && 'else' === node.name;
332
+
333
+ // tooling
334
+
335
+ // transform @import at-rules
336
+ function transformImportAtrule(rule, opts) {
337
+ // if @import is supported
338
+ if (opts.transform.includes('@import')) {
339
+ // @import options
340
+ const { id, media, cwf, cwd } = getImportOpts(rule, opts);
341
+
342
+ // PostCSS options
343
+ const options = opts.result.opts;
344
+ const parser = options.parser || options.syntax && options.syntax.parse || null;
345
+
346
+ if (
347
+ opts.importFilter instanceof Function && opts.importFilter(id, media) ||
348
+ opts.importFilter instanceof RegExp && opts.importFilter.test(id)
349
+ ) {
350
+ const cwds = [cwd].concat(opts.importPaths);
351
+
352
+ // promise the resolved file and its contents using the file resolver
353
+ const importPromise = cwds.reduce(
354
+ (promise, thiscwd) => promise.catch(
355
+ () => opts.importResolve(id, thiscwd, opts)
356
+ ),
357
+ Promise.reject()
358
+ );
359
+
360
+ return importPromise.then(
361
+ // promise the processed file
362
+ ({ file, contents }) => processor.process(contents, { from: file, parser: parser }).then(
363
+ ({ root }) => {
364
+ // push a dependency message
365
+ opts.result.messages.push({ type: 'dependency', file: file, parent: cwf });
366
+
367
+ // imported nodes
368
+ const nodes = root.nodes.slice(0);
369
+
370
+ // if media params were detected
371
+ if (media) {
372
+ // create a new media rule
373
+ const mediaRule = postcss.atRule({
374
+ name: 'media',
375
+ params: media,
376
+ source: rule.source
377
+ });
378
+
379
+ // append with the imported nodes
380
+ mediaRule.append(nodes);
381
+
382
+ // replace the @import at-rule with the @media at-rule
383
+ rule.replaceWith(mediaRule);
384
+ } else {
385
+ // replace the @import at-rule with the imported nodes
386
+ rule.replaceWith(nodes);
387
+ }
388
+
389
+ // transform all nodes from the import
390
+ return transformNode({ nodes }, opts);
391
+ }
392
+ ),
393
+ () => {
394
+ // otherwise, if the @import could not be found
395
+ manageUnresolved(rule, opts, '@import', `Could not resolve the @import for "${id}"`);
396
+ }
397
+ )
398
+ }
399
+ }
400
+ }
401
+
402
+ const noopPlugin = () => {
403
+ return { postcssPlugin: 'noop-plugin', Once() {} };
404
+ };
405
+
406
+ noopPlugin.postcss = true;
407
+
408
+ const processor = postcss([noopPlugin()]);
409
+
410
+ // return the @import statement options (@import ID, @import ID MEDIA)
411
+ const getImportOpts = (node, opts) => {
412
+ const [ rawid, ...medias ] = list.space(node.params);
413
+ const id = getReplacedString(trimWrappingURL(rawid), node, opts);
414
+ const media = medias.join(' ');
415
+
416
+ // current working file and directory
417
+ const cwf = node.source && node.source.input && node.source.input.file || opts.result.from;
418
+ const cwd = cwf ? path.dirname(cwf) : opts.importRoot;
419
+
420
+ return { id, media, cwf, cwd };
421
+ };
422
+
423
+ // return a string with the wrapping url() and quotes trimmed
424
+ const trimWrappingURL = string => trimWrappingQuotes(string.replace(/^url\(([\W\w]*)\)$/, '$1'));
425
+
426
+ // return a string with the wrapping quotes trimmed
427
+ const trimWrappingQuotes = string => string.replace(/^("|')([\W\w]*)\1$/, '$2');
428
+
429
+ // tooling
430
+
431
+ // transform @include at-rules
432
+ function transformIncludeAtrule(rule, opts) {
433
+ // if @include is supported
434
+ if (opts.transform.includes('@include')) {
435
+ // @include options
436
+ const { name, args } = getIncludeOpts(rule);
437
+
438
+ // the closest @mixin variable
439
+ const mixin = getClosestVariable(`@mixin ${name}`, rule.parent, opts);
440
+
441
+ // if the @mixin variable exists
442
+ if (mixin) {
443
+ // set @mixin variables on the @include at-rule
444
+ mixin.params.forEach(
445
+ (param, index) => {
446
+ const arg = index in args
447
+ ? getReplacedString(args[index], rule, opts)
448
+ : param.value;
449
+
450
+ setVariable(rule, param.name, arg, opts);
451
+ }
452
+ );
453
+
454
+ // clone the @mixin at-rule
455
+ const clone = mixin.rule.clone({
456
+ original: rule,
457
+ parent: rule.parent,
458
+ variables: rule.variables
459
+ });
460
+
461
+ // transform the clone children
462
+ return transformNode(clone, opts).then(() => {
463
+ // replace the @include at-rule with the clone children
464
+ rule.parent.insertBefore(rule, clone.nodes);
465
+
466
+ rule.remove();
467
+ })
468
+ } else {
469
+ // otherwise, if the @mixin variable does not exist
470
+ manageUnresolved(rule, opts, name, `Could not resolve the mixin for "${name}"`);
471
+ }
472
+ }
473
+ }
474
+
475
+ // return the @include statement options (@include NAME, @include NAME(ARGS))
476
+ const getIncludeOpts = node => {
477
+ // @include name and args
478
+ const [ name, sourceArgs ] = node.params.split(matchOpeningParen$1, 2);
479
+ const args = sourceArgs
480
+ ? list.comma(sourceArgs.slice(0, -1))
481
+ : [];
482
+
483
+ return { name, args };
484
+ };
485
+
486
+ // match an opening parenthesis
487
+ const matchOpeningParen$1 = /(?<!var)\(/;
488
+
489
+ // tooling
490
+
491
+ // transform @for at-rules
492
+ function transformForAtrule(rule, opts) {
493
+ // if @for is supported
494
+ if (opts.transform.includes('@for')) {
495
+ // @for options
496
+ const { varname, start, end, increment } = getForOpts(rule, opts);
497
+ const direction = start <= end ? 1 : -1;
498
+ const replacements = [];
499
+ const ruleClones = [];
500
+
501
+ // for each iteration
502
+ for (let incrementor = start; incrementor * direction <= end * direction; incrementor += increment * direction) {
503
+ // set the current variable
504
+ setVariable(rule, varname, incrementor, opts);
505
+
506
+ // clone the @for at-rule
507
+ const clone = rule.clone({
508
+ parent: rule.parent,
509
+ variables: Object.assign({}, rule.variables)
510
+ });
511
+
512
+ ruleClones.push(clone);
513
+ }
514
+
515
+ return waterfall(
516
+ ruleClones,
517
+ clone => transformNode(clone, opts).then(
518
+ () => {
519
+ replacements.push(...clone.nodes);
520
+ }
521
+ )
522
+ ).then(
523
+ () => {
524
+ // replace the @for at-rule with the replacements
525
+ rule.parent.insertBefore(rule, replacements);
526
+
527
+ rule.remove();
528
+ }
529
+ )
530
+ }
531
+ }
532
+
533
+ // return the @for statement options (@for NAME from START through END, @for NAME from START through END by INCREMENT)
534
+ const getForOpts = (node, opts) => {
535
+ const params = list.space(node.params);
536
+ const varname = params[0].trim().slice(1);
537
+ const start = Number(getReplacedString(params[2], node, opts));
538
+ const end = Number(getReplacedString(params[4], node, opts));
539
+ const increment = 6 in params && Number(getReplacedString(params[6], node, opts)) || 1;
540
+
541
+ return { varname, start, end, increment };
542
+ };
543
+
544
+ // tooling
545
+
546
+ // transform @mixin at-rules
547
+ function transformMixinAtrule(rule, opts) {
548
+ // if @mixin is supported
549
+ if (opts.transform.includes('@mixin')) {
550
+ // @mixin options
551
+ const { name, params } = getMixinOpts(rule, opts);
552
+
553
+ // set the mixin as a variable on the parent of the @mixin at-rule
554
+ setVariable(rule.parent, `@mixin ${name}`, { params, rule }, opts);
555
+
556
+ // remove the @mixin at-rule
557
+ rule.remove();
558
+ }
559
+ }
560
+
561
+ // return the @mixin statement options (@mixin NAME, @mixin NAME(PARAMS))
562
+ const getMixinOpts = (node, opts) => {
563
+ // @mixin name and default params ([{ name, value }, ...])
564
+ const [ name, sourceParams ] = node.params.split(matchOpeningParen, 2);
565
+ const params = sourceParams && sourceParams.slice(0, -1).trim()
566
+ ? list.comma(sourceParams.slice(0, -1).trim()).map(
567
+ param => {
568
+ const parts = list.split(param, ':');
569
+ const paramName = parts[0].slice(1);
570
+ const paramValue = parts.length > 1
571
+ ? getReplacedString(parts.slice(1).join(':'), node, opts)
572
+ : undefined;
573
+
574
+ return { name: paramName, value: paramValue };
575
+ }
576
+ )
577
+ : [];
578
+
579
+ return { name, params };
580
+ };
581
+
582
+ // match an opening parenthesis
583
+ const matchOpeningParen = /(?<!var)\(/;
584
+
585
+ // tooling
586
+
587
+ // transform rule nodes
588
+ function transformRule(rule, opts) {
589
+ // update the rule selector with its variables replaced by their corresponding values
590
+ rule.selector = getReplacedString(rule.selector, rule, opts);
591
+ }
592
+
593
+ // tooling
594
+
595
+ // transform @content at-rules
596
+ function transformContentAtrule(rule, opts) {
597
+ // if @content is supported
598
+ if (opts.transform.includes('@content')) {
599
+ // the closest @mixin at-rule
600
+ const mixin = getClosestMixin(rule);
601
+
602
+ // if the @mixin at-rule exists
603
+ if (mixin) {
604
+ // clone the @mixin at-rule
605
+ const clone = mixin.original.clone({
606
+ parent: rule.parent,
607
+ variables: rule.variables
608
+ });
609
+
610
+ // transform the clone children
611
+ return transformNode(clone, opts).then(() => {
612
+ // replace the @content at-rule with the clone children
613
+ rule.parent.insertBefore(rule, clone.nodes);
614
+
615
+ rule.remove();
616
+ })
617
+
618
+ } else {
619
+ // otherwise, if the @mixin at-rule does not exist
620
+ manageUnresolved(rule, opts, '@content', 'Could not resolve the mixin for @content');
621
+ }
622
+ }
623
+ }
624
+
625
+ // return the closest @mixin at-rule
626
+ const getClosestMixin = node => 'atrule' === node.type && 'mixin' === node.name
627
+ ? node
628
+ : node.parent && getClosestMixin(node.parent);
629
+
630
+ // tooling
631
+
632
+ function transformNode(node, opts) {
633
+ return waterfall(
634
+ getNodesArray(node),
635
+ child => transformRuleOrDecl(child, opts).then(() => {
636
+ // conditionally walk the children of the attached child
637
+ if (child.parent) {
638
+ return transformNode(child, opts);
639
+ }
640
+ })
641
+ );
642
+ }
643
+
644
+ function transformRuleOrDecl(child, opts) {
645
+ return Promise.resolve().then(() => {
646
+ const type = child.type;
647
+
648
+ if ('atrule' === type) {
649
+ const name = (child.name || '').toLowerCase();
650
+
651
+ if ('content' === name) {
652
+ // transform @content at-rules
653
+ return transformContentAtrule(child, opts);
654
+ } else if ('each' === name) {
655
+ // transform @each at-rules
656
+ return transformEachAtrule(child, opts);
657
+ } else if ('if' === name) {
658
+ // transform @if at-rules
659
+ return transformIfAtrule(child, opts);
660
+ } else if ('import' === name) {
661
+ return transformImportAtrule(child, opts);
662
+ } else if ('include' === name) {
663
+ // transform @include at-rules
664
+ return transformIncludeAtrule(child, opts);
665
+ } else if ('for' === name) {
666
+ // transform @for at-rules
667
+ return transformForAtrule(child, opts);
668
+ } else if ('mixin' === name) {
669
+ // transform mixin at-rules
670
+ return transformMixinAtrule(child, opts);
671
+ } else {
672
+ // transform all other at-rules
673
+ return transformAtrule(child, opts);
674
+ }
675
+ } else if ('decl' === type) {
676
+ // transform declarations
677
+ return transformDecl(child, opts);
678
+ } else if ('rule' === type) {
679
+ // transform rule
680
+ return transformRule(child, opts);
681
+ }
682
+ })
683
+ }
684
+
685
+ // return the children of a node as an array
686
+ const getNodesArray = node => Array.from(Object(node).nodes || []);
687
+
688
+ // tooling
689
+
690
+ const matchProtocol = /^(?:[a-zA-Z]+:)?\/\//;
691
+
692
+ // plugin
693
+ const plugin = opts => ({
694
+ postcssPlugin: "postcss-advanced-variables",
695
+ Once(root, { result }) {
696
+ // process options
697
+ const transformOpt = ['@content', '@each', '@else', '@if', '@include', '@import', '@for', '@mixin'].filter(
698
+ feature => !String(Object(opts).disable || '').split(/\s*,\s*|\s+,?\s*|\s,?\s+/).includes(feature)
699
+ );
700
+ const unresolvedOpt = String(Object(opts).unresolved || 'throw').toLowerCase();
701
+ const variablesOpt = Object(opts).variables;
702
+ const importCache = Object(Object(opts).importCache);
703
+ const importFilter = Object(opts).importFilter || (id => {
704
+ return !matchProtocol.test(id);
705
+ });
706
+ const importPaths = [].concat(Object(opts).importPaths || []);
707
+ const importResolve = Object(opts).importResolve || (
708
+ (id, cwd) => resolve(id, { cwd, readFile: true, cache: importCache })
709
+ );
710
+ const importRoot = Object(opts).importRoot || process.cwd();
711
+
712
+ return transformNode(root, {
713
+ result,
714
+ importCache,
715
+ importFilter,
716
+ importPaths,
717
+ importResolve,
718
+ importRoot,
719
+ transform: transformOpt,
720
+ unresolved: unresolvedOpt,
721
+ variables: variablesOpt
722
+ });
723
+ },
724
+ });
725
+
726
+ plugin.postcss = true;
727
+
728
+ export { plugin as default };