@minko-fe/postcss-pxtorem
Version:
A postcss plugin that converts px to rem.
497 lines (490 loc) • 16.4 kB
JavaScript
// src/index.ts
import { cloneDeep, isArray as isArray3, isBoolean } from "@minko-fe/lodash-pro";
// src/utils/index.ts
import { isArray as isArray2, isFunction, isRegExp, isString } from "@minko-fe/lodash-pro";
// src/utils/constant.ts
var MAYBE_REGEXP = ["selectorBlackList", "exclude", "include"];
var DISABLE_NEXT_COMMENT = "pxtorem-disable-next-line";
// src/utils/parse-query.ts
import { isArray, isObject } from "@minko-fe/lodash-pro";
import decodeComponent from "decode-uri-component";
import splitOnFirst from "split-on-first";
function validateArrayFormatSeparator(value) {
if (typeof value !== "string" || value.length !== 1) {
throw new TypeError("arrayFormatSeparator must be single character string");
}
}
function parse(query, options) {
options = {
decode: true,
sort: true,
arrayFormat: "none",
arrayFormatSeparator: ",",
parseNumbers: false,
parseBooleans: false,
...options
};
validateArrayFormatSeparator(options.arrayFormatSeparator);
const formatter = parserForArrayFormat(options);
const returnValue = /* @__PURE__ */ Object.create(null);
if (typeof query !== "string") {
return returnValue;
}
query = query.trim().replace(/^[#&?]/, "");
if (!query) {
return returnValue;
}
for (const parameter of query.split("&")) {
if (parameter === "") {
continue;
}
const parameter_ = options.decode ? parameter.replaceAll("+", " ") : parameter;
let [key, value] = splitOnFirst(parameter_, "=");
if (key === void 0) {
key = parameter_;
}
value = value === void 0 ? null : ["comma", "separator", "bracket-separator"].includes(options.arrayFormat) ? value : decode(value, options);
formatter(decode(key, options), value, returnValue);
}
for (const [key, value] of Object.entries(returnValue)) {
if (typeof value === "object" && value !== null) {
for (const [key2, value2] of Object.entries(value)) {
value[key2] = parseValue(value2, options);
}
} else {
returnValue[key] = parseValue(value, options);
}
}
if (options.sort === false) {
return returnValue;
}
return (options.sort === true ? Object.keys(returnValue).sort() : Object.keys(returnValue).sort(options.sort)).reduce(
(result, key) => {
const value = returnValue[key];
if (Boolean(value) && isObject(value) && !isArray(value)) {
result[key] = keysSorter(value);
} else {
result[key] = value;
}
return result;
},
/* @__PURE__ */ Object.create(null)
);
}
function parserForArrayFormat(options) {
let result;
switch (options.arrayFormat) {
case "index": {
return (key, value, accumulator) => {
result = /\[(\d*)]$/.exec(key);
key = key.replace(/\[\d*]$/, "");
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === void 0) {
accumulator[key] = {};
}
accumulator[key][result[1]] = value;
};
}
case "bracket": {
return (key, value, accumulator) => {
result = /(\[])$/.exec(key);
key = key.replace(/\[]$/, "");
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === void 0) {
accumulator[key] = [value];
return;
}
accumulator[key] = [...accumulator[key], value];
};
}
case "colon-list-separator": {
return (key, value, accumulator) => {
result = /(:list)$/.exec(key);
key = key.replace(/:list$/, "");
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === void 0) {
accumulator[key] = [value];
return;
}
accumulator[key] = [...accumulator[key], value];
};
}
case "comma":
case "separator": {
return (key, value, accumulator) => {
const isArray4 = typeof value === "string" && value.includes(options.arrayFormatSeparator);
const isEncodedArray = typeof value === "string" && !isArray4 && decode(value, options).includes(options.arrayFormatSeparator);
value = isEncodedArray ? decode(value, options) : value;
const newValue = isArray4 || isEncodedArray ? value.split(options.arrayFormatSeparator).map((item) => decode(item, options)) : value === null ? value : decode(value, options);
accumulator[key] = newValue;
};
}
case "bracket-separator": {
return (key, value, accumulator) => {
const isArray4 = /(\[])$/.test(key);
key = key.replace(/\[]$/, "");
if (!isArray4) {
accumulator[key] = value ? decode(value, options) : value;
return;
}
const arrayValue = value === null ? [] : value.split(options.arrayFormatSeparator).map((item) => decode(item, options));
if (accumulator[key] === void 0) {
accumulator[key] = arrayValue;
return;
}
accumulator[key] = [...accumulator[key], ...arrayValue];
};
}
default: {
return (key, value, accumulator) => {
if (accumulator[key] === void 0) {
accumulator[key] = value;
return;
}
accumulator[key] = [...[accumulator[key]].flat(), value];
};
}
}
}
function decode(value, options) {
if (options.decode) {
return decodeComponent(value);
}
return value;
}
function parseValue(value, options) {
if (options.parseNumbers && !Number.isNaN(Number(value)) && typeof value === "string" && value.trim() !== "") {
value = Number(value);
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === "true" || value.toLowerCase() === "false")) {
value = value.toLowerCase() === "true";
}
return value;
}
function keysSorter(input) {
if (isArray(input)) {
return input.sort();
}
if (typeof input === "object") {
return keysSorter(Object.keys(input)).sort((a, b) => Number(a) - Number(b)).map((key) => input[key]);
}
return input;
}
// src/utils/index.ts
function reRegExp() {
return /^\/((?:\\\/|[^/])+)\/([gimy]*)$/;
}
var filterPropList = Object.freeze({
exact(list) {
return list.filter((m) => m.match(/^[^!*]+$/));
},
contain(list) {
return list.filter((m) => m.match(/^\*.+\*$/)).map((m) => m.slice(1, -1));
},
endWith(list) {
return list.filter((m) => m.match(/^\*[^*]+$/)).map((m) => m.slice(1));
},
startWith(list) {
return list.filter((m) => m.match(/^[^!*]+\*$/)).map((m) => m.slice(0, Math.max(0, m.length - 1)));
},
notExact(list) {
return list.filter((m) => m.match(/^![^*].*$/)).map((m) => m.slice(1));
},
notContain(list) {
return list.filter((m) => m.match(/^!\*.+\*$/)).map((m) => m.slice(2, -1));
},
notEndWith(list) {
return list.filter((m) => m.match(/^!\*[^*]+$/)).map((m) => m.slice(2));
},
notStartWith(list) {
return list.filter((m) => m.match(/^![^*]+\*$/)).map((m) => m.slice(1, -1));
}
});
function initOptions(options) {
return Object.assign({}, DEFAULT_OPTIONS, options);
}
function isOptionComment(node) {
return (node == null ? void 0 : node.type) === "comment";
}
var processd = Symbol("processed");
function isRepeatRun(r) {
if (!r)
return false;
if (Reflect.get(r, processd)) {
return true;
}
Reflect.set(r, processd, true);
return false;
}
function parseRegExp(maybeRegExpArg) {
var _a, _b;
const RE_REGEXP = reRegExp();
if (isString(maybeRegExpArg) && RE_REGEXP.test(maybeRegExpArg)) {
return new RegExp(((_a = RE_REGEXP.exec(maybeRegExpArg)) == null ? void 0 : _a[1]) || "", (_b = RE_REGEXP.exec(maybeRegExpArg)) == null ? void 0 : _b[2]);
}
return maybeRegExpArg;
}
var isPxtoremReg = /(?<=^pxtorem\?).+/g;
function getOptionsFromComment(comment, parseOptions) {
var _a, _b;
try {
const index = comment.text.search(isPxtoremReg);
const ret = {};
let query = comment.text.slice(index);
if (!query || index === -1)
return ret;
query = query.replace(/\s+/g, "");
const defaultKeys = Object.keys(DEFAULT_OPTIONS);
const parsed = parse(query, {
parseBooleans: true,
parseNumbers: true,
arrayFormat: "bracket-separator",
arrayFormatSeparator: "|",
...parseOptions
});
const RE_REGEXP = reRegExp();
for (const k of Object.keys(parsed)) {
if (defaultKeys.includes(k)) {
let cur = parsed[k];
if (MAYBE_REGEXP.includes(k)) {
if (isArray2(cur)) {
cur = cur.map((t) => {
return parseRegExp(t);
});
} else if (isString(cur) && RE_REGEXP.test(cur)) {
cur = parseRegExp(cur);
}
}
ret[k] = cur;
}
}
return ret;
} catch {
console.warn("Unexpected comment", { start: (_a = comment.source) == null ? void 0 : _a.start, end: (_b = comment.source) == null ? void 0 : _b.end });
}
}
function createPropListMatcher(filterList) {
const hasWild = filterList.includes("*");
const matchAll = hasWild && filterList.length === 1;
const lists = {
exact: filterPropList.exact(filterList),
contain: filterPropList.contain(filterList),
startWith: filterPropList.startWith(filterList),
endWith: filterPropList.endWith(filterList),
notExact: filterPropList.notExact(filterList),
notContain: filterPropList.notContain(filterList),
notStartWith: filterPropList.notStartWith(filterList),
notEndWith: filterPropList.notEndWith(filterList)
};
return function(prop) {
if (matchAll)
return true;
return (hasWild || lists.exact.includes(prop) || lists.contain.some((m) => prop.includes(m)) || lists.startWith.some((m) => prop.indexOf(m) === 0) || lists.endWith.some((m) => prop.indexOf(m) === prop.length - m.length)) && !(lists.notExact.includes(prop) || lists.notContain.some((m) => prop.includes(m)) || lists.notStartWith.some((m) => prop.indexOf(m) === 0) || lists.notEndWith.some((m) => prop.indexOf(m) === prop.length - m.length));
};
}
function toFixed(number, precision) {
const multiplier = 10 ** (precision + 1);
const wholeNumber = Math.floor(number * multiplier);
return Math.round(wholeNumber / 10) * 10 / multiplier;
}
function createPxReplace(rootValue, unitPrecision, minPixelValue) {
return (m, $1) => {
if (!$1)
return m;
const pixels = Number.parseFloat($1);
if (pixels <= minPixelValue)
return m;
const fixedVal = toFixed(pixels / rootValue, unitPrecision);
return fixedVal === 0 ? "0" : `${fixedVal}rem`;
};
}
function blacklistedSelector(blacklist, selector) {
if (!isString(selector))
return;
return blacklist.some((t) => {
if (isString(t)) {
return selector.includes(t);
}
return selector.match(t);
});
}
function declarationExists(decls, prop, value) {
return decls.some((decl) => {
return decl.prop === prop && decl.value === value;
});
}
function isXClude(Xclude, filePath) {
return Xclude && filePath && (isFunction(Xclude) && Xclude(filePath) || isString(Xclude) && filePath.includes(Xclude) || isRegExp(Xclude) && Xclude.test(filePath));
}
function judgeIsExclude(exclude, include, filePath) {
if (isXClude(exclude, filePath)) {
return true;
}
if (include) {
if (isXClude(include, filePath)) {
return false;
}
return true;
}
return false;
}
function convertUnitFn(value, convert) {
if (typeof convert.source === "string") {
return value.replace(new RegExp(`${convert.source}$`), convert.target);
} else if (isRegExp(convert.source)) {
return value.replace(new RegExp(convert.source), convert.target);
}
throw new Error("convertUnit source must be string or RegExp");
}
function checkIfDisable(p) {
const { disable, isExcludeFile, r } = p;
return disable || isExcludeFile || isRepeatRun(r);
}
var OPTION_SYMBOL = Symbol("OPTION_SYMBOL");
function setupCurrentOptions(h, {
node,
comment
}) {
var _a;
if (isOptionComment(comment)) {
h[OPTION_SYMBOL].originOpts = {
...h[OPTION_SYMBOL].originOpts,
...getOptionsFromComment(comment, h[OPTION_SYMBOL].originOpts.parseOptions)
};
}
const { originOpts } = h[OPTION_SYMBOL];
const { include, exclude, rootValue, disable } = originOpts;
h[OPTION_SYMBOL].isExcludeFile = judgeIsExclude(exclude, include, (_a = node == null ? void 0 : node.source) == null ? void 0 : _a.input.file);
if (checkIfDisable({ disable, isExcludeFile: h[OPTION_SYMBOL].isExcludeFile })) {
return;
}
h[OPTION_SYMBOL].originOpts.rootValue = isFunction(rootValue) ? rootValue(node == null ? void 0 : node.source.input) : rootValue;
h[OPTION_SYMBOL].pxReplace = createPxReplace(
h[OPTION_SYMBOL].originOpts.rootValue,
originOpts.unitPrecision,
originOpts.minPixelValue
);
}
// src/utils/pixel-unit-regex.ts
function getUnitRegexp(unit) {
return new RegExp(`"[^"]+"|'[^']+'|url\\([^\\)]+\\)|--[\\w-]+|(\\d*\\.?\\d+)${unit}`, "g");
}
// src/index.ts
var DEFAULT_OPTIONS = {
rootValue: 16,
unitToConvert: "px",
unitPrecision: 5,
selectorBlackList: [],
propList: ["*"],
replace: true,
atRules: false,
minPixelValue: 0,
include: null,
exclude: null,
disable: false,
convertUnit: false,
parseOptions: {}
};
var postcssPlugin = "postcss-pxtorem";
function pxtorem(options) {
const RAW_OPTIONS = initOptions(options);
const plugin = {
postcssPlugin,
Once(r, h) {
const node = r.root();
const firstNode = node.nodes[0];
h[OPTION_SYMBOL] = {
isExcludeFile: false,
pxReplace: void 0,
originOpts: cloneDeep(RAW_OPTIONS)
// avoid reference pollution
};
setupCurrentOptions(h, { node, comment: firstNode });
},
Comment(node, h) {
setupCurrentOptions(h, { node, comment: node });
},
CommentExit(comment) {
var _a;
if ((_a = comment.text.match(isPxtoremReg)) == null ? void 0 : _a.length) {
comment.remove();
}
},
Declaration(decl, h) {
const opts = h[OPTION_SYMBOL].originOpts;
if (checkIfDisable({ disable: opts.disable, isExcludeFile: h[OPTION_SYMBOL].isExcludeFile, r: decl })) {
return;
}
const satisfyPropList = createPropListMatcher(opts.propList);
if (!decl.value.includes(opts.unitToConvert) || !satisfyPropList(decl.prop) || blacklistedSelector(opts.selectorBlackList, decl.parent.selector)) {
return;
}
const prev = decl.prev();
if ((prev == null ? void 0 : prev.type) === "comment" && prev.text === DISABLE_NEXT_COMMENT) {
prev.remove();
return;
}
const pxRegex = getUnitRegexp(opts.unitToConvert);
const value = h[OPTION_SYMBOL].pxReplace ? decl.value.replace(pxRegex, h[OPTION_SYMBOL].pxReplace) : decl.value;
if (declarationExists(decl.parent, decl.prop, value))
return;
if (opts.replace) {
decl.value = value;
} else {
decl.cloneAfter({ value });
}
},
DeclarationExit(decl, h) {
const opts = h[OPTION_SYMBOL].originOpts;
const { convertUnit } = opts;
if (convertUnit) {
if (isArray3(convertUnit)) {
decl.value = convertUnit.reduce((c, conv) => {
return convertUnitFn(c, conv);
}, decl.value);
} else {
decl.value = convertUnitFn(decl.value, convertUnit);
}
}
},
AtRule(atRule, h) {
const opts = h[OPTION_SYMBOL].originOpts;
if (checkIfDisable({ disable: opts.disable, isExcludeFile: h[OPTION_SYMBOL].isExcludeFile, r: atRule })) {
return;
}
function replacePxInRules() {
if (!atRule.params.includes(opts.unitToConvert))
return;
const pxRegex = getUnitRegexp(opts.unitToConvert);
atRule.params = h[OPTION_SYMBOL].pxReplace ? atRule.params.replace(pxRegex, h[OPTION_SYMBOL].pxReplace) : atRule.params;
}
if (isBoolean(opts.atRules) && opts.atRules) {
replacePxInRules();
}
if (isArray3(opts.atRules) && opts.atRules.length > 0 && opts.atRules.includes(atRule.name)) {
replacePxInRules();
}
}
};
if (options == null ? void 0 : options.disable) {
return {
postcssPlugin
};
}
return plugin;
}
pxtorem.postcss = true;
var src_default = pxtorem;
export {
DEFAULT_OPTIONS,
src_default as default,
pxtorem
};