UNPKG

@minko-fe/postcss-pxtorem

Version:
497 lines (490 loc) 16.4 kB
// 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 };