UNPKG

postcss-mobile-forever

Version:

PostCSS 伸缩视图转换插件。To adapt different displays by one mobile viewport.

390 lines (359 loc) 14.9 kB
const { unitContentMatchReg, fixedUnitContentReg } = require("./regexs"); const { containingBlockWidthProps, horisontalContainingBlockLogicalProps, verticleContainingBlockLogicalProps } = require("./constants"); const { vwToMediaQueryPx, pxToMediaQueryPx, noUnitZeroToMediaQueryPx_FIXED_LR, pxToMediaQueryPx_FIXED_LR, vwToMediaQueryPx_FIXED_LR, percentToMediaQueryPx_FIXED_LR, percentToMediaQueryPx_FIXED, pxToMaxViewUnit, vwToMaxViewUnit, pxToViewUnit, pxToMaxViewUnit_FIXED_LR, vwToMaxViewUnit_FIXED_LR, percentToMaxViewUnit_FIXED_LR, percentageToMaxViewUnit, pxToClampLength, vwToClampLength, percentageToClampLength, pxToRemUnit, vwToRemUnit, pxToRemUnit_FIXED_LR, vwToRemUnit_FIXED_LR, percentageToRemUnit_FIXED_LR, percentageToRemUnit } = require("./unit-transfer"); /** 创建 fixed 时依赖宽度的属性 map */ const createContainingBlockWidthDecls = (isVerticalWritingMode) => { /** 逻辑宽度属性,和包含块有关的属性 */ const logicalWidthProps = isVerticalWritingMode ? verticleContainingBlockLogicalProps : horisontalContainingBlockLogicalProps; /** 所有和根包含块宽度有关的属性 */ const allContainingBlockWidthProps = containingBlockWidthProps.concat(logicalWidthProps); const mapArray = allContainingBlockWidthProps.reduce((prev, cur) => { return prev.concat([[cur, null]]); }, []); return new Map(mapArray); } /** 移除重复属性 */ const removeDuplicateDecls = (node) => { node.walkRules(rule => { const walked = { props: [], propNodes: [] } rule.walkDecls(decl => { const prop = decl.prop; const i = walked.props.indexOf(prop); if (i > -1) { const important = decl.important; const prevImportant = walked.propNodes[i].important; if (important || (!important && !prevImportant)) { walked.propNodes[i].remove(); walked.propNodes[i] = decl; } } else { walked.props.push(prop); walked.propNodes.push(decl); } }); }); }; /** 合并相同名称的选择器 */ const mergeRules = (node) => { const walked = { rules: [], selectors: [] }; node.walkRules(rule => { const compositeSelector = rule.selector.concat(rule.parent.name).concat(rule.parent.params); // 将区别类似 @k a { %0 { top: 0 } } 和 @k b { %0 { top: 0 } } const i = walked.selectors.indexOf(compositeSelector); if (i > -1) { walked.rules[i].append(rule.nodes); rule.remove(); } else { walked.rules.push(rule); walked.selectors.push(compositeSelector); } }); }; /** 检查是否是正则类型或包含正则的数组 */ const createRegArrayChecker = (TYPE_REG, TYPE_ARY) => (options, optionName) => { const obj2Str = val => Object.prototype.toString.call(val); const option = options[optionName]; if (!option) return null; if (obj2Str(option) === '[object RegExp]') return TYPE_REG; if (obj2Str(option) === '[object Array]') { let bad = false; for (let i = 0; i < option.length; ++ i) { if (obj2Str(option[i]) !== '[object RegExp]') { bad = true; break; } } if (!bad) return TYPE_ARY; } throw new Error('options.' + optionName + ' should be RegExp or Array of RegExp.'); }; /** 如果不包含,则返回 true,不转换 */ const createIncludeFunc = (TYPE_REG, TYPE_ARY) => (include, file, regOrAry) => { if (include && file) { if (regOrAry === TYPE_REG) { if (!include.test(file)) return true; } if (regOrAry === TYPE_ARY) { let book = false; for (let i = 0; i < include.length; ++ i) { if(include[i].test(file)) { book = true; break; } } if (!book) return true; } } }; /** 如果排除,则返回 true,不转换 */ const createExcludeFunc = (TYPE_REG, TYPE_ARY) => (exclude, file, regOrAry) => { if (exclude && file) { if (regOrAry === TYPE_REG) { if (exclude.test(file)) return true; } if (regOrAry === TYPE_ARY) { for (let i = 0; i < exclude.length; ++ i) if (exclude[i].test(file)) return true; } } }; const isMatchedStr = (list, str) => { if (typeof str !== 'string') return; return list.some((regex) => { if (typeof regex === 'string') return str === regex; return str.match(regex); }); }; /** 是否匹配选择器属性 */ const isMatchedSelectorProperty = (propertyBlackList, selector, prop) => { const propertyBlackListAry = [].concat(propertyBlackList); for(const property of propertyBlackListAry) { if (Object.prototype.toString.call(property) === "[object Object]") { for(const propSelector in property) { if (property.hasOwnProperty(propSelector) && propSelector === selector) { const propValue = [].concat(property[propSelector]); if (isMatchedStr(propValue, prop)) return true; } } } else { if (isMatchedStr([property], prop)) return true; } } }; /** 是否有忽略转换的注释? */ const hasIgnoreComments = (decl, result, IN_CMT, IL_CMT) => { let ignore = false; ignore = hasNextComment(decl, IN_CMT); if (!ignore) { ignore = hasPrevComment(decl, IL_CMT, result); } return ignore; }; /** 当前行是否有注释? */ const hasPrevComment = (node, comment, result) => { let bud = false; let next = node.next(); do { if (next && next.type === 'comment' && next.text === comment) { if (/\n/.test(next.raws.before)) { result.warn('Unexpected comment /* ' + comment + ' */ must be after declaration at same line.', { node: next }); } else { // remove comment next.remove(); bud = true; } break; } if (next == null || next.type !== "comment") break; } while(next = next.next()) return bud; }; /** 前面是否有匹配注释? */ const hasNextComment = (node, comment) => { let bud = false; let prev = node.prev(); if (prev == null) return false; do { if (prev && prev.type === 'comment' && prev.text === comment) { // remove comment prev.remove(); bud = true; break; } if (prev == null || prev.type !== "comment") break; } while(prev = prev.prev()) return bud; }; const hasApplyWithoutConvertComment = (decl, result, AWC_CMT) => { return hasPrevComment(decl, AWC_CMT, result); }; /** 选择器上方有根包含块的注释 */ const hasRootContainingBlockComment = hasNextComment; /** 选择器前面有非根包含块的注释吗 */ const hasNoneRootContainingBlockComment = hasNextComment; /** 选择器前面有书写模式的注释吗 */ const hasWritingModeComment = hasNextComment; /** 获取匹配的数字和单位,转换 */ const convertPropValue = (prop, val, { enabledMobile, enabledDesktop, enabledLandscape, convertMobile, convertDesktop, convertLandscape, matchPercentage, }) => { let book = false; let mobileVal = ''; let desktopVal = ''; let landscapeVal = ''; let matched = null; let lastIndex = 0; const reg = matchPercentage ? fixedUnitContentReg : unitContentMatchReg; while(matched = reg.exec(val)) { const numberStr = matched[2]; if (numberStr == null) continue; book = true; const beforePxContent = matched[1] || ''; const chunk = val.slice(lastIndex, matched.index + beforePxContent.length); // 当前匹配和上一次匹配之间的字符串 const number = Number(numberStr); // 数字 const lengthUnit = matched[3]; // 单位 if (convertMobile && enabledMobile) mobileVal = mobileVal.concat(chunk, convertMobile(number, lengthUnit, numberStr)); if (convertDesktop && enabledDesktop) desktopVal = desktopVal.concat(chunk, convertDesktop(number, lengthUnit, numberStr)); if (convertLandscape && enabledLandscape) landscapeVal = landscapeVal.concat(chunk, convertLandscape(number, lengthUnit, numberStr)); lastIndex = reg.lastIndex; } const tailChunk = val.slice(lastIndex, val.length); // 最后一次匹配到结尾的字符串 return { mobile: enabledMobile ? mobileVal.concat(tailChunk) : val, desktop: enabledDesktop ? desktopVal.concat(tailChunk) : val, landscape: enabledLandscape ? landscapeVal.concat(tailChunk) : val, book, } }; /** 转换包含块是根元素的媒体查询 */ const convertFixedMediaQuery = (number, idealWidth, viewportWidth, precision, unit, numberStr, isFixed, leftOrRight) => { if (isFixed) { if (leftOrRight) { if (unit === "px") { return pxToMediaQueryPx_FIXED_LR(number, viewportWidth, idealWidth, precision); } else if (unit === "vw") { return vwToMediaQueryPx_FIXED_LR(number, idealWidth, precision); } else if (unit === '%') { return percentToMediaQueryPx_FIXED_LR(number, idealWidth, precision); } else if (unit === "" || unit === " ") { if (number === 0) return noUnitZeroToMediaQueryPx_FIXED_LR(idealWidth); return `${number}${unit}`; } else return `${number}${unit}`; } else { if (unit === "px") { return pxToMediaQueryPx(number, viewportWidth, idealWidth, precision, numberStr); } else if (unit === '%') { return percentToMediaQueryPx_FIXED(number, idealWidth, precision, numberStr); } else if (unit === "vw") { return vwToMediaQueryPx(number, idealWidth, precision, numberStr); } else return `${number}${unit}`; } } else { return convertNoFixedMediaQuery(number, idealWidth, viewportWidth, precision, unit, numberStr); } }; /** 转换媒体查询 */ const convertNoFixedMediaQuery = (number, idealWidth, viewportWidth, precision, unit, numberStr) => { if (unit === "vw") return vwToMediaQueryPx(number, idealWidth, precision, numberStr); else if (unit === "px") { return pxToMediaQueryPx(number, viewportWidth, idealWidth, precision, numberStr); } else return `${number}${unit}`; }; /** 转换移动竖屏 */ const convertMobile = (prop, number, unit, viewportWidth, unitPrecision, fontViewportUnit, viewportUnit) => { if (unit === "px") return pxToViewUnit(prop, number, unit, viewportWidth, unitPrecision, fontViewportUnit, viewportUnit); else return `${number}${unit}`; }; /** 转换移动竖屏,限制最大宽度 */ const convertMaxMobile = (number, unit, maxDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop, numberStr, minDisplayWidth) => { const isClamp = minDisplayWidth != null; if (unit === "px") return isClamp ? pxToClampLength(number, maxDisplayWidth, minDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop) : pxToMaxViewUnit(number, maxDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop); else if (unit === "vw") return isClamp ? vwToClampLength(number, maxDisplayWidth, minDisplayWidth, numberStr, unitPrecision) : vwToMaxViewUnit(number, maxDisplayWidth, numberStr, unitPrecision); else return `${number}${unit}`; } /** 转换非 fixed 定位的 px,向 rem 转换 */ const convertRem = (number, unit, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop, basicRemRatio) => { if (unit === "px") { return pxToRemUnit(number, unitPrecision, viewportUnit, fontViewportUnit, prop, basicRemRatio); } else if (unit === "vw") { return vwToRemUnit(number, viewportWidth, unitPrecision); } else return `${number}${unit}`; } /** 转换移动端竖屏,包含块是根元素,left、right 属性 */ const convertMaxMobile_FIXED_LR = (number, unit, maxDisplayWidth, viewportWidth, unitPrecision, numberStr) => { if (unit === "px") return pxToMaxViewUnit_FIXED_LR(number, maxDisplayWidth, viewportWidth, unitPrecision); else if (unit === "vw") return vwToMaxViewUnit_FIXED_LR(number, maxDisplayWidth, unitPrecision); else if (unit === '%') return percentToMaxViewUnit_FIXED_LR(number, maxDisplayWidth, unitPrecision); else if (unit === " " || unit === "") { if (number === 0) return `calc(50% - min(50%, ${maxDisplayWidth / 2}px))`; return `${number}${unit}`; } else return `${numberStr}${unit}`; }; const convertRem_FIXED_LR = (number, unit, viewportWidth, unitPrecision, numberStr, basicRemRatio) => { if (unit === "px") return pxToRemUnit_FIXED_LR(number, viewportWidth, unitPrecision, basicRemRatio); else if (unit === "vw") return vwToRemUnit_FIXED_LR(number, viewportWidth, unitPrecision); else if (unit === '%') return percentageToRemUnit_FIXED_LR(number, viewportWidth, unitPrecision); else if (unit === " " || unit === "") { if (number === 0) return `calc(50% - ${viewportWidth / 200}rem)`; return `${number}${unit}`; } else return `${numberStr}${unit}`; }; /** 转换移动端竖屏,包含块是根元素 */ const convertMaxMobile_FIXED = (number, unit, maxDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop, numberStr, minDisplayWidth) => { const isClamp = minDisplayWidth != null; if (unit === "px") { return isClamp ? pxToClampLength(number, maxDisplayWidth, minDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop) : pxToMaxViewUnit(number, maxDisplayWidth, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop); } else if (unit === "vw") { return isClamp ? vwToClampLength(number, maxDisplayWidth, minDisplayWidth, numberStr, unitPrecision) : vwToMaxViewUnit(number, maxDisplayWidth, numberStr, unitPrecision); } else if (unit === '%') { return isClamp ? percentageToClampLength(number, maxDisplayWidth, minDisplayWidth, numberStr, unitPrecision) : percentageToMaxViewUnit(number, maxDisplayWidth, numberStr, unitPrecision); } else return `${numberStr}${unit}`; }; const convertRem_FIXED = (number, unit, viewportWidth, unitPrecision, viewportUnit, fontViewportUnit, prop, numberStr, basicRemRatio) => { if (unit === "px") { return pxToRemUnit(number, unitPrecision, viewportUnit, fontViewportUnit, prop, basicRemRatio); } else if (unit === "vw") { return vwToRemUnit(number, viewportWidth, unitPrecision); } else if (unit === '%') { return percentageToRemUnit(number, viewportWidth, unitPrecision); } else return `${numberStr}${unit}`; }; module.exports = { removeDuplicateDecls, mergeRules, createRegArrayChecker, createIncludeFunc, createExcludeFunc, isMatchedStr, isMatchedSelectorProperty, convertPropValue, hasIgnoreComments, createContainingBlockWidthDecls, hasNoneRootContainingBlockComment, hasRootContainingBlockComment, hasWritingModeComment, convertNoFixedMediaQuery, convertFixedMediaQuery, convertMobile, convertMaxMobile, convertMaxMobile_FIXED_LR, convertMaxMobile_FIXED, hasApplyWithoutConvertComment, convertRem, convertRem_FIXED_LR, convertRem_FIXED, };