index.js 5.0 KB

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