index.js 5.3 KB

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