You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
5.0 KiB
174 lines
5.0 KiB
const postcss = require("postcss"); |
|
const pxRegex = require("./lib/pixel-unit-regex"); |
|
const filterPropList = require("./lib/filter-prop-list"); |
|
const type = require("./lib/type"); |
|
|
|
const defaults = { |
|
rootValue: 16, |
|
unitPrecision: 5, |
|
selectorBlackList: [], |
|
propList: ["font", "font-size", "line-height", "letter-spacing"], |
|
replace: true, |
|
mediaQuery: false, |
|
minPixelValue: 0, |
|
exclude: null |
|
}; |
|
|
|
const legacyOptions = { |
|
root_value: "rootValue", |
|
unit_precision: "unitPrecision", |
|
selector_black_list: "selectorBlackList", |
|
prop_white_list: "propList", |
|
media_query: "mediaQuery", |
|
propWhiteList: "propList" |
|
}; |
|
|
|
module.exports = postcss.plugin("postcss-pxtorem", options => { |
|
convertLegacyOptions(options); |
|
const opts = Object.assign({}, defaults, options); |
|
const satisfyPropList = createPropListMatcher(opts.propList); |
|
|
|
return css => { |
|
const exclude = opts.exclude; |
|
const filePath = css.source.input.file; |
|
if ( |
|
exclude && |
|
((type.isFunction(exclude) && exclude(filePath)) || |
|
(type.isString(exclude) && filePath.indexOf(exclude) !== -1) || |
|
filePath.match(exclude) !== null) |
|
) { |
|
return; |
|
} |
|
|
|
const rootValue = |
|
typeof opts.rootValue === "function" |
|
? opts.rootValue(css.source.input) |
|
: opts.rootValue; |
|
const pxReplace = createPxReplace( |
|
rootValue, |
|
opts.unitPrecision, |
|
opts.minPixelValue |
|
); |
|
|
|
css.walkDecls((decl, i) => { |
|
if ( |
|
decl.value.indexOf("px") === -1 || |
|
!satisfyPropList(decl.prop) || |
|
blacklistedSelector(opts.selectorBlackList, decl.parent.selector) |
|
) |
|
return; |
|
|
|
const value = decl.value.replace(pxRegex, pxReplace); |
|
|
|
// if rem unit already exists, do not add or replace |
|
if (declarationExists(decl.parent, decl.prop, value)) return; |
|
|
|
if (opts.replace) { |
|
decl.value = value; |
|
} else { |
|
decl.parent.insertAfter(i, decl.clone({ value: value })); |
|
} |
|
}); |
|
|
|
if (opts.mediaQuery) { |
|
css.walkAtRules("media", rule => { |
|
if (rule.params.indexOf("px") === -1) return; |
|
rule.params = rule.params.replace(pxRegex, pxReplace); |
|
}); |
|
} |
|
}; |
|
}); |
|
|
|
function convertLegacyOptions(options) { |
|
if (typeof options !== "object") return; |
|
if ( |
|
((typeof options["prop_white_list"] !== "undefined" && |
|
options["prop_white_list"].length === 0) || |
|
(typeof options.propWhiteList !== "undefined" && |
|
options.propWhiteList.length === 0)) && |
|
typeof options.propList === "undefined" |
|
) { |
|
options.propList = ["*"]; |
|
delete options["prop_white_list"]; |
|
delete options.propWhiteList; |
|
} |
|
Object.keys(legacyOptions).forEach(key => { |
|
if (Reflect.has(options, key)) { |
|
options[legacyOptions[key]] = options[key]; |
|
delete options[key]; |
|
} |
|
}); |
|
} |
|
|
|
function createPxReplace(rootValue, unitPrecision, minPixelValue) { |
|
return (m, $1) => { |
|
if (!$1) return m; |
|
const pixels = parseFloat($1); |
|
if (pixels < minPixelValue) return m; |
|
const fixedVal = toFixed(pixels / rootValue, unitPrecision); |
|
return fixedVal === 0 ? "0" : fixedVal + "rem"; |
|
}; |
|
} |
|
|
|
function toFixed(number, precision) { |
|
const multiplier = Math.pow(10, precision + 1), |
|
wholeNumber = Math.floor(number * multiplier); |
|
return (Math.round(wholeNumber / 10) * 10) / multiplier; |
|
} |
|
|
|
function declarationExists(decls, prop, value) { |
|
return decls.some(decl => decl.prop === prop && decl.value === value); |
|
} |
|
|
|
function blacklistedSelector(blacklist, selector) { |
|
if (typeof selector !== "string") return; |
|
return blacklist.some(regex => { |
|
if (typeof regex === "string") { |
|
return selector.indexOf(regex) !== -1; |
|
} |
|
return selector.match(regex); |
|
}); |
|
} |
|
|
|
function createPropListMatcher(propList) { |
|
const hasWild = propList.indexOf("*") > -1; |
|
const matchAll = hasWild && propList.length === 1; |
|
const lists = { |
|
exact: filterPropList.exact(propList), |
|
contain: filterPropList.contain(propList), |
|
startWith: filterPropList.startWith(propList), |
|
endWith: filterPropList.endWith(propList), |
|
notExact: filterPropList.notExact(propList), |
|
notContain: filterPropList.notContain(propList), |
|
notStartWith: filterPropList.notStartWith(propList), |
|
notEndWith: filterPropList.notEndWith(propList) |
|
}; |
|
return prop => { |
|
if (matchAll) return true; |
|
return ( |
|
(hasWild || |
|
lists.exact.indexOf(prop) > -1 || |
|
lists.contain.some(function(m) { |
|
return prop.indexOf(m) > -1; |
|
}) || |
|
lists.startWith.some(function(m) { |
|
return prop.indexOf(m) === 0; |
|
}) || |
|
lists.endWith.some(function(m) { |
|
return prop.indexOf(m) === prop.length - m.length; |
|
})) && |
|
!( |
|
lists.notExact.indexOf(prop) > -1 || |
|
lists.notContain.some(function(m) { |
|
return prop.indexOf(m) > -1; |
|
}) || |
|
lists.notStartWith.some(function(m) { |
|
return prop.indexOf(m) === 0; |
|
}) || |
|
lists.notEndWith.some(function(m) { |
|
return prop.indexOf(m) === prop.length - m.length; |
|
}) |
|
) |
|
); |
|
}; |
|
}
|
|
|