index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. 'use strict';
  2. const browserslist = require('browserslist');
  3. const valueParser = require('postcss-value-parser');
  4. const regexLowerCaseUPrefix = /^u(?=\+)/;
  5. /**
  6. * @param {string} range
  7. * @return {string}
  8. */
  9. function unicode(range) {
  10. const values = range.slice(2).split('-');
  11. if (values.length < 2) {
  12. return range;
  13. }
  14. const left = values[0].split('');
  15. const right = values[1].split('');
  16. if (left.length !== right.length) {
  17. return range;
  18. }
  19. const merged = mergeRangeBounds(left, right);
  20. if (merged) {
  21. return merged;
  22. }
  23. return range;
  24. }
  25. /**
  26. * @param {string[]} left
  27. * @param {string[]} right
  28. * @return {false|string}
  29. */
  30. function mergeRangeBounds(left, right) {
  31. let questionCounter = 0;
  32. let group = 'u+';
  33. for (const [index, value] of left.entries()) {
  34. if (value === right[index] && questionCounter === 0) {
  35. group = group + value;
  36. } else if (value === '0' && right[index] === 'f') {
  37. questionCounter++;
  38. group = group + '?';
  39. } else {
  40. return false;
  41. }
  42. }
  43. // The maximum number of wildcard characters (?) for ranges is 5.
  44. if (questionCounter < 6) {
  45. return group;
  46. } else {
  47. return false;
  48. }
  49. }
  50. /**
  51. * IE and Edge before 16 version ignore the unicode-range if the 'U' is lowercase
  52. *
  53. * https://caniuse.com/#search=unicode-range
  54. *
  55. * @param {string} browser
  56. * @return {boolean}
  57. */
  58. function hasLowerCaseUPrefixBug(browser) {
  59. return browserslist('ie <=11, edge <= 15').includes(browser);
  60. }
  61. /**
  62. * @param {string} value
  63. * @return {string}
  64. */
  65. function transform(value, isLegacy = false) {
  66. return valueParser(value)
  67. .walk((child) => {
  68. if (child.type === 'unicode-range') {
  69. const transformed = unicode(child.value.toLowerCase());
  70. child.value = isLegacy
  71. ? transformed.replace(regexLowerCaseUPrefix, 'U')
  72. : transformed;
  73. }
  74. return false;
  75. })
  76. .toString();
  77. }
  78. /**
  79. * @type {import('postcss').PluginCreator<void>}
  80. * @return {import('postcss').Plugin}
  81. */
  82. function pluginCreator() {
  83. return {
  84. postcssPlugin: 'postcss-normalize-unicode',
  85. /** @param {import('postcss').Result & {opts: browserslist.Options}} result*/
  86. prepare(result) {
  87. const cache = new Map();
  88. const resultOpts = result.opts || {};
  89. const browsers = browserslist(null, {
  90. stats: resultOpts.stats,
  91. path: __dirname,
  92. env: resultOpts.env,
  93. });
  94. const isLegacy = browsers.some(hasLowerCaseUPrefixBug);
  95. return {
  96. OnceExit(css) {
  97. css.walkDecls(/^unicode-range$/i, (decl) => {
  98. const value = decl.value;
  99. if (cache.has(value)) {
  100. decl.value = cache.get(value);
  101. return;
  102. }
  103. const newValue = transform(value, isLegacy);
  104. decl.value = newValue;
  105. cache.set(value, newValue);
  106. });
  107. },
  108. };
  109. },
  110. };
  111. }
  112. pluginCreator.postcss = true;
  113. module.exports = pluginCreator;