ensureCompatibility.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.pseudoElements = undefined;
  6. exports.default = ensureCompatibility;
  7. var _caniuseApi = require('caniuse-api');
  8. var _postcssSelectorParser = require('postcss-selector-parser');
  9. var _postcssSelectorParser2 = _interopRequireDefault(_postcssSelectorParser);
  10. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  11. const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;
  12. const cssSel2 = 'css-sel2';
  13. const cssSel3 = 'css-sel3';
  14. const cssGencontent = 'css-gencontent';
  15. const cssFirstLetter = 'css-first-letter';
  16. const cssFirstLine = 'css-first-line';
  17. const cssInOutOfRange = 'css-in-out-of-range';
  18. const pseudoElements = exports.pseudoElements = {
  19. ':active': cssSel2,
  20. ':after': cssGencontent,
  21. ':before': cssGencontent,
  22. ':checked': cssSel3,
  23. ':default': 'css-default-pseudo',
  24. ':dir': 'css-dir-pseudo',
  25. ':disabled': cssSel3,
  26. ':empty': cssSel3,
  27. ':enabled': cssSel3,
  28. ':first-child': cssSel2,
  29. ':first-letter': cssFirstLetter,
  30. ':first-line': cssFirstLine,
  31. ':first-of-type': cssSel3,
  32. ':focus': cssSel2,
  33. ':focus-within': 'css-focus-within',
  34. ':has': 'css-has',
  35. ':hover': cssSel2,
  36. ':in-range': cssInOutOfRange,
  37. ':indeterminate': 'css-indeterminate-pseudo',
  38. ':lang': cssSel2,
  39. ':last-child': cssSel3,
  40. ':last-of-type': cssSel3,
  41. ':matches': 'css-matches-pseudo',
  42. ':not': cssSel3,
  43. ':nth-child': cssSel3,
  44. ':nth-last-child': cssSel3,
  45. ':nth-last-of-type': cssSel3,
  46. ':nth-of-type': cssSel3,
  47. ':only-child': cssSel3,
  48. ':only-of-type': cssSel3,
  49. ':optional': 'css-optional-pseudo',
  50. ':out-of-range': cssInOutOfRange,
  51. ':placeholder-shown': 'css-placeholder-shown',
  52. ':root': cssSel3,
  53. ':target': cssSel3,
  54. '::after': cssGencontent,
  55. '::backdrop': 'dialog',
  56. '::before': cssGencontent,
  57. '::first-letter': cssFirstLetter,
  58. '::first-line': cssFirstLine,
  59. '::marker': 'css-marker-pseudo',
  60. '::placeholder': 'css-placeholder',
  61. '::selection': 'css-selection'
  62. };
  63. function isCssMixin(selector) {
  64. return selector[selector.length - 1] === ':';
  65. }
  66. const isSupportedCache = {};
  67. // Move to util in future
  68. function isSupportedCached(feature, browsers) {
  69. const key = JSON.stringify({ feature, browsers });
  70. let result = isSupportedCache[key];
  71. if (!result) {
  72. result = (0, _caniuseApi.isSupported)(feature, browsers);
  73. isSupportedCache[key] = result;
  74. }
  75. return result;
  76. }
  77. function ensureCompatibility(selectors, browsers, compatibilityCache) {
  78. // Should not merge mixins
  79. if (selectors.some(isCssMixin)) {
  80. return false;
  81. }
  82. return selectors.every(selector => {
  83. if (simpleSelectorRe.test(selector)) {
  84. return true;
  85. }
  86. if (compatibilityCache && selector in compatibilityCache) {
  87. return compatibilityCache[selector];
  88. }
  89. let compatible = true;
  90. (0, _postcssSelectorParser2.default)(ast => {
  91. ast.walk(node => {
  92. const { type, value } = node;
  93. if (type === 'pseudo') {
  94. const entry = pseudoElements[value];
  95. if (entry && compatible) {
  96. compatible = isSupportedCached(entry, browsers);
  97. }
  98. }
  99. if (type === 'combinator') {
  100. if (~value.indexOf('~')) {
  101. compatible = isSupportedCached(cssSel3, browsers);
  102. }
  103. if (~value.indexOf('>') || ~value.indexOf('+')) {
  104. compatible = isSupportedCached(cssSel2, browsers);
  105. }
  106. }
  107. if (type === 'attribute' && node.attribute) {
  108. // [foo]
  109. if (!node.operator) {
  110. compatible = isSupportedCached(cssSel2, browsers);
  111. }
  112. if (value) {
  113. // [foo="bar"], [foo~="bar"], [foo|="bar"]
  114. if (~['=', '~=', '|='].indexOf(node.operator)) {
  115. compatible = isSupportedCached(cssSel2, browsers);
  116. }
  117. // [foo^="bar"], [foo$="bar"], [foo*="bar"]
  118. if (~['^=', '$=', '*='].indexOf(node.operator)) {
  119. compatible = isSupportedCached(cssSel3, browsers);
  120. }
  121. }
  122. // [foo="bar" i]
  123. if (node.insensitive) {
  124. compatible = isSupportedCached('css-case-insensitive', browsers);
  125. }
  126. }
  127. if (!compatible) {
  128. // If this node was not compatible,
  129. // break out early from walking the rest
  130. return false;
  131. }
  132. });
  133. }).processSync(selector);
  134. if (compatibilityCache) {
  135. compatibilityCache[selector] = compatible;
  136. }
  137. return compatible;
  138. });
  139. }