index.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. "use strict";
  2. const postcss = require("postcss");
  3. const pxRegex = require("./lib/pixel-unit-regex");
  4. const filterPropList = require("./lib/filter-prop-list");
  5. const defaults = {
  6. rootValue: 16,
  7. unitPrecision: 5,
  8. selectorBlackList: [],
  9. propList: ["font", "font-size", "line-height", "letter-spacing"],
  10. replace: true,
  11. mediaQuery: false,
  12. minPixelValue: 0
  13. };
  14. const legacyOptions = {
  15. root_value: "rootValue",
  16. unit_precision: "unitPrecision",
  17. selector_black_list: "selectorBlackList",
  18. prop_white_list: "propList",
  19. media_query: "mediaQuery",
  20. propWhiteList: "propList"
  21. };
  22. module.exports = postcss.plugin("postcss-pxtorem", options => {
  23. convertLegacyOptions(options);
  24. const opts = Object.assign({}, defaults, options);
  25. const satisfyPropList = createPropListMatcher(opts.propList);
  26. return css => {
  27. const rootValue =
  28. typeof opts.rootValue === "function"
  29. ? opts.rootValue(css.source.input)
  30. : opts.rootValue;
  31. const pxReplace = createPxReplace(
  32. rootValue,
  33. opts.unitPrecision,
  34. opts.minPixelValue
  35. );
  36. css.walkDecls((decl, i) => {
  37. if (
  38. decl.value.indexOf("px") === -1 ||
  39. !satisfyPropList(decl.prop) ||
  40. blacklistedSelector(opts.selectorBlackList, decl.parent.selector)
  41. )
  42. return;
  43. const value = decl.value.replace(pxRegex, pxReplace);
  44. // if rem unit already exists, do not add or replace
  45. if (declarationExists(decl.parent, decl.prop, value)) return;
  46. if (opts.replace) {
  47. decl.value = value;
  48. } else {
  49. decl.parent.insertAfter(i, decl.clone({ value: value }));
  50. }
  51. });
  52. if (opts.mediaQuery) {
  53. css.walkAtRules("media", rule => {
  54. if (rule.params.indexOf("px") === -1) return;
  55. rule.params = rule.params.replace(pxRegex, pxReplace);
  56. });
  57. }
  58. };
  59. });
  60. function convertLegacyOptions(options) {
  61. if (typeof options !== "object") return;
  62. if (
  63. ((typeof options["prop_white_list"] !== "undefined" &&
  64. options["prop_white_list"].length === 0) ||
  65. (typeof options.propWhiteList !== "undefined" &&
  66. options.propWhiteList.length === 0)) &&
  67. typeof options.propList === "undefined"
  68. ) {
  69. options.propList = ["*"];
  70. delete options["prop_white_list"];
  71. delete options.propWhiteList;
  72. }
  73. Object.keys(legacyOptions).forEach(key => {
  74. if (Reflect.has(options, key)) {
  75. options[legacyOptions[key]] = options[key];
  76. delete options[key];
  77. }
  78. });
  79. }
  80. function createPxReplace(rootValue, unitPrecision, minPixelValue) {
  81. return (m, $1) => {
  82. if (!$1) return m;
  83. const pixels = parseFloat($1);
  84. if (pixels < minPixelValue) return m;
  85. const fixedVal = toFixed(pixels / rootValue, unitPrecision);
  86. return fixedVal === 0 ? "0" : fixedVal + "rem";
  87. };
  88. }
  89. function toFixed(number, precision) {
  90. const multiplier = Math.pow(10, precision + 1),
  91. wholeNumber = Math.floor(number * multiplier);
  92. return (Math.round(wholeNumber / 10) * 10) / multiplier;
  93. }
  94. function declarationExists(decls, prop, value) {
  95. return decls.some(decl => decl.prop === prop && decl.value === value);
  96. }
  97. function blacklistedSelector(blacklist, selector) {
  98. if (typeof selector !== "string") return;
  99. return blacklist.some(regex => {
  100. if (typeof regex === "string") {
  101. return selector.indexOf(regex) !== -1;
  102. }
  103. return selector.match(regex);
  104. });
  105. }
  106. function createPropListMatcher(propList) {
  107. const hasWild = propList.indexOf("*") > -1;
  108. const matchAll = hasWild && propList.length === 1;
  109. const lists = {
  110. exact: filterPropList.exact(propList),
  111. contain: filterPropList.contain(propList),
  112. startWith: filterPropList.startWith(propList),
  113. endWith: filterPropList.endWith(propList),
  114. notExact: filterPropList.notExact(propList),
  115. notContain: filterPropList.notContain(propList),
  116. notStartWith: filterPropList.notStartWith(propList),
  117. notEndWith: filterPropList.notEndWith(propList)
  118. };
  119. return prop => {
  120. if (matchAll) return true;
  121. return (
  122. (hasWild ||
  123. lists.exact.indexOf(prop) > -1 ||
  124. lists.contain.some(function(m) {
  125. return prop.indexOf(m) > -1;
  126. }) ||
  127. lists.startWith.some(function(m) {
  128. return prop.indexOf(m) === 0;
  129. }) ||
  130. lists.endWith.some(function(m) {
  131. return prop.indexOf(m) === prop.length - m.length;
  132. })) &&
  133. !(
  134. lists.notExact.indexOf(prop) > -1 ||
  135. lists.notContain.some(function(m) {
  136. return prop.indexOf(m) > -1;
  137. }) ||
  138. lists.notStartWith.some(function(m) {
  139. return prop.indexOf(m) === 0;
  140. }) ||
  141. lists.notEndWith.some(function(m) {
  142. return prop.indexOf(m) === prop.length - m.length;
  143. })
  144. )
  145. );
  146. };
  147. }