2217 lines
72 KiB
JavaScript
2217 lines
72 KiB
JavaScript
'use strict';
|
||
|
||
var shared = require('@vue/shared');
|
||
var parseCSSFont = require('parse-css-font');
|
||
var postcss = require('postcss');
|
||
|
||
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
||
|
||
var parseCSSFont__default = /*#__PURE__*/_interopDefault(parseCSSFont);
|
||
var postcss__default = /*#__PURE__*/_interopDefault(postcss);
|
||
|
||
const COMBINATORS_RE = /^((?:(?:\.[A-Za-z0-9_\-]+)+[\+\~\> ])*)((?:\.[A-Za-z0-9_\-\:]+)+)$/;
|
||
// 单个类选择器或类串联选择器,支持逗号分隔(如 .class1, .class1.class2, .class1,.class2,.class3.class4)
|
||
const SIMPLE_SELECTOR_RE = /^(?:\.[A-Za-z0-9_\-]+)+(?:\s*,\s*(?:\.[A-Za-z0-9_\-]+)+)*$/;
|
||
function createDecl(prop, value, important, raws, source) {
|
||
const decl = {
|
||
type: 'decl',
|
||
prop,
|
||
value: value.toString(),
|
||
raws,
|
||
source,
|
||
};
|
||
if (important) {
|
||
decl.important = true;
|
||
}
|
||
return decl;
|
||
}
|
||
const NUM_REGEXP = /^[-]?\d*\.?\d+$/;
|
||
const LENGTH_REGEXP = /^[-+]?\d*\.?\d+(\S*)$/;
|
||
const SUPPORTED_VALUES_REGEXP = /supported values are: ([^)]+)/;
|
||
const SUPPORT_CSS_UNIT = ['px', 'pt', 'wx', 'upx', 'rpx'];
|
||
const isNumber = (val) => typeof val === 'number';
|
||
const cacheStringFunction = (fn) => {
|
||
const cache = Object.create(null);
|
||
return ((str) => {
|
||
const hit = cache[str];
|
||
return hit || (cache[str] = fn(str));
|
||
});
|
||
};
|
||
const hyphenateRE = /([A-Z])/g;
|
||
const hyphenateStyleProperty = cacheStringFunction((str) => str
|
||
.replace(hyphenateRE, (_, m) => {
|
||
if (typeof m === 'string') {
|
||
return '-' + m.toLowerCase();
|
||
}
|
||
return m;
|
||
})
|
||
.toLowerCase());
|
||
function autofixedReason(v, result) {
|
||
return 'NOTE: property value `' + v + '` is autofixed to `' + result + '`';
|
||
}
|
||
function validReason(k, v) {
|
||
return ('ERROR: property value `' +
|
||
v +
|
||
'` is not valid for `' +
|
||
hyphenateStyleProperty(k) +
|
||
'`');
|
||
}
|
||
function defaultValueReason(k, v) {
|
||
return ('NOTE: property value `' +
|
||
v +
|
||
'` is the DEFAULT value for `' +
|
||
hyphenateStyleProperty(k) +
|
||
'` (could be removed)');
|
||
}
|
||
function supportedEnumReason(k, v, items) {
|
||
return ('ERROR: property value `' +
|
||
v +
|
||
'` is not supported for `' +
|
||
hyphenateStyleProperty(k) +
|
||
'` (supported values are: `' +
|
||
items.join('`|`') +
|
||
'`)');
|
||
}
|
||
function supportedValueWithTipsReason(k, v, tips) {
|
||
return ('ERROR: property value `' +
|
||
v +
|
||
'` is not supported for `' +
|
||
hyphenateStyleProperty(k) +
|
||
'` ' +
|
||
tips);
|
||
}
|
||
function supportedUnitWithAutofixedReason(unit, v, result) {
|
||
return ('NOTE: unit `' +
|
||
unit +
|
||
'` is not supported and property value `' +
|
||
v +
|
||
'` is autofixed to `' +
|
||
result +
|
||
'`');
|
||
}
|
||
function compatibilityReason(k) {
|
||
return ('NOTE: the ' +
|
||
hyphenateStyleProperty(k) +
|
||
' property may have compatibility problem on native');
|
||
}
|
||
function supportedPropertyReason(k) {
|
||
return ('WARNING: `' +
|
||
hyphenateStyleProperty(k) +
|
||
'` is not a standard property name (may not be supported)');
|
||
}
|
||
function getPlatformVersion(platform) {
|
||
return isDom2() ? platform?.unixVaporVer : platform?.unixVer;
|
||
}
|
||
function isDom2() {
|
||
return typeof process !== 'undefined' && process.env.UNI_APP_X_DOM2 === 'true';
|
||
}
|
||
function getSupportedPlatforms(uniPlatform) {
|
||
const supportedPlatforms = [];
|
||
if (getPlatformVersion(uniPlatform?.app?.android) !== 'x') {
|
||
supportedPlatforms.push('app-android');
|
||
}
|
||
if (getPlatformVersion(uniPlatform?.app?.ios) !== 'x') {
|
||
supportedPlatforms.push('app-ios');
|
||
}
|
||
if (getPlatformVersion(uniPlatform?.app?.harmony) !== 'x') {
|
||
supportedPlatforms.push('app-harmony');
|
||
}
|
||
return supportedPlatforms;
|
||
}
|
||
function normalizeReasons(reasons, k, v) {
|
||
let enums = [];
|
||
for (let i = 0; i < reasons.length; i++) {
|
||
const reason = reasons[i];
|
||
if (SUPPORTED_VALUES_REGEXP.test(reason)) {
|
||
const match = reason.match(SUPPORTED_VALUES_REGEXP);
|
||
if (match) {
|
||
const values = match[1]
|
||
.split('|')
|
||
.map((item) => item.replace(/`/g, ''))
|
||
.filter(Boolean);
|
||
enums.push(...values);
|
||
reasons.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
}
|
||
if (enums.length > 0) {
|
||
enums = [...new Set(enums)];
|
||
reasons.push(supportedEnumReason(k, v, enums));
|
||
}
|
||
return reasons;
|
||
}
|
||
// http://www.w3.org/TR/css3-color/#svg-color
|
||
const EXTENDED_COLOR_KEYWORDS = {
|
||
aliceblue: '#F0F8FF',
|
||
antiquewhite: '#FAEBD7',
|
||
aqua: '#00FFFF',
|
||
aquamarine: '#7FFFD4',
|
||
azure: '#F0FFFF',
|
||
beige: '#F5F5DC',
|
||
bisque: '#FFE4C4',
|
||
black: '#000000',
|
||
blanchedalmond: '#FFEBCD',
|
||
blue: '#0000FF',
|
||
blueviolet: '#8A2BE2',
|
||
brown: '#A52A2A',
|
||
burlywood: '#DEB887',
|
||
cadetblue: '#5F9EA0',
|
||
chartreuse: '#7FFF00',
|
||
chocolate: '#D2691E',
|
||
coral: '#FF7F50',
|
||
cornflowerblue: '#6495ED',
|
||
cornsilk: '#FFF8DC',
|
||
crimson: '#DC143C',
|
||
cyan: '#00FFFF',
|
||
darkblue: '#00008B',
|
||
darkcyan: '#008B8B',
|
||
darkgoldenrod: '#B8860B',
|
||
darkgray: '#A9A9A9',
|
||
darkgreen: '#006400',
|
||
darkgrey: '#A9A9A9',
|
||
darkkhaki: '#BDB76B',
|
||
darkmagenta: '#8B008B',
|
||
darkolivegreen: '#556B2F',
|
||
darkorange: '#FF8C00',
|
||
darkorchid: '#9932CC',
|
||
darkred: '#8B0000',
|
||
darksalmon: '#E9967A',
|
||
darkseagreen: '#8FBC8F',
|
||
darkslateblue: '#483D8B',
|
||
darkslategray: '#2F4F4F',
|
||
darkslategrey: '#2F4F4F',
|
||
darkturquoise: '#00CED1',
|
||
darkviolet: '#9400D3',
|
||
deeppink: '#FF1493',
|
||
deepskyblue: '#00BFFF',
|
||
dimgray: '#696969',
|
||
dimgrey: '#696969',
|
||
dodgerblue: '#1E90FF',
|
||
firebrick: '#B22222',
|
||
floralwhite: '#FFFAF0',
|
||
forestgreen: '#228B22',
|
||
fuchsia: '#FF00FF',
|
||
gainsboro: '#DCDCDC',
|
||
ghostwhite: '#F8F8FF',
|
||
gold: '#FFD700',
|
||
goldenrod: '#DAA520',
|
||
gray: '#808080',
|
||
green: '#008000',
|
||
greenyellow: '#ADFF2F',
|
||
grey: '#808080',
|
||
honeydew: '#F0FFF0',
|
||
hotpink: '#FF69B4',
|
||
indianred: '#CD5C5C',
|
||
indigo: '#4B0082',
|
||
ivory: '#FFFFF0',
|
||
khaki: '#F0E68C',
|
||
lavender: '#E6E6FA',
|
||
lavenderblush: '#FFF0F5',
|
||
lawngreen: '#7CFC00',
|
||
lemonchiffon: '#FFFACD',
|
||
lightblue: '#ADD8E6',
|
||
lightcoral: '#F08080',
|
||
lightcyan: '#E0FFFF',
|
||
lightgoldenrodyellow: '#FAFAD2',
|
||
lightgray: '#D3D3D3',
|
||
lightgreen: '#90EE90',
|
||
lightgrey: '#D3D3D3',
|
||
lightpink: '#FFB6C1',
|
||
lightsalmon: '#FFA07A',
|
||
lightseagreen: '#20B2AA',
|
||
lightskyblue: '#87CEFA',
|
||
lightslategray: '#778899',
|
||
lightslategrey: '#778899',
|
||
lightsteelblue: '#B0C4DE',
|
||
lightyellow: '#FFFFE0',
|
||
lime: '#00FF00',
|
||
limegreen: '#32CD32',
|
||
linen: '#FAF0E6',
|
||
magenta: '#FF00FF',
|
||
maroon: '#800000',
|
||
mediumaquamarine: '#66CDAA',
|
||
mediumblue: '#0000CD',
|
||
mediumorchid: '#BA55D3',
|
||
mediumpurple: '#9370DB',
|
||
mediumseagreen: '#3CB371',
|
||
mediumslateblue: '#7B68EE',
|
||
mediumspringgreen: '#00FA9A',
|
||
mediumturquoise: '#48D1CC',
|
||
mediumvioletred: '#C71585',
|
||
midnightblue: '#191970',
|
||
mintcream: '#F5FFFA',
|
||
mistyrose: '#FFE4E1',
|
||
moccasin: '#FFE4B5',
|
||
navajowhite: '#FFDEAD',
|
||
navy: '#000080',
|
||
oldlace: '#FDF5E6',
|
||
olive: '#808000',
|
||
olivedrab: '#6B8E23',
|
||
orange: '#FFA500',
|
||
orangered: '#FF4500',
|
||
orchid: '#DA70D6',
|
||
palegoldenrod: '#EEE8AA',
|
||
palegreen: '#98FB98',
|
||
paleturquoise: '#AFEEEE',
|
||
palevioletred: '#DB7093',
|
||
papayawhip: '#FFEFD5',
|
||
peachpuff: '#FFDAB9',
|
||
peru: '#CD853F',
|
||
pink: '#FFC0CB',
|
||
plum: '#DDA0DD',
|
||
powderblue: '#B0E0E6',
|
||
purple: '#800080',
|
||
red: '#FF0000',
|
||
rosybrown: '#BC8F8F',
|
||
royalblue: '#4169E1',
|
||
saddlebrown: '#8B4513',
|
||
salmon: '#FA8072',
|
||
sandybrown: '#F4A460',
|
||
seagreen: '#2E8B57',
|
||
seashell: '#FFF5EE',
|
||
sienna: '#A0522D',
|
||
silver: '#C0C0C0',
|
||
skyblue: '#87CEEB',
|
||
slateblue: '#6A5ACD',
|
||
slategray: '#708090',
|
||
slategrey: '#708090',
|
||
snow: '#FFFAFA',
|
||
springgreen: '#00FF7F',
|
||
steelblue: '#4682B4',
|
||
tan: '#D2B48C',
|
||
teal: '#008080',
|
||
thistle: '#D8BFD8',
|
||
tomato: '#FF6347',
|
||
turquoise: '#40E0D0',
|
||
violet: '#EE82EE',
|
||
wheat: '#F5DEB3',
|
||
white: '#FFFFFF',
|
||
whitesmoke: '#F5F5F5',
|
||
yellow: '#FFFF00',
|
||
yellowgreen: '#9ACD32',
|
||
};
|
||
// from uni-api/ canvas
|
||
function checkColor(e) {
|
||
// 其他开发者适配的echarts会传入一个undefined到这里
|
||
e = e || '#000000';
|
||
let t = null;
|
||
if ((t = /^#([0-9|A-F|a-f]{6})$/.exec(e)) != null) {
|
||
const n = parseInt(t[1].slice(0, 2), 16);
|
||
const o = parseInt(t[1].slice(2, 4), 16);
|
||
const r = parseInt(t[1].slice(4), 16);
|
||
return [n, o, r, 255];
|
||
}
|
||
if ((t = /^#([0-9|A-F|a-f]{3})$/.exec(e)) != null) {
|
||
let n = t[1].slice(0, 1);
|
||
let o = t[1].slice(1, 2);
|
||
let r = t[1].slice(2, 3);
|
||
n = parseInt(n + n, 16);
|
||
o = parseInt(o + o, 16);
|
||
r = parseInt(r + r, 16);
|
||
return [n, o, r, 255];
|
||
}
|
||
if ((t = /^rgb\((.+)\)$/.exec(e)) != null) {
|
||
return t[1]
|
||
.split(',')
|
||
.map(function (e) {
|
||
return Math.min(255, parseInt(e.trim()));
|
||
})
|
||
.concat(255);
|
||
}
|
||
if ((t = /^rgba\((.+)\)$/.exec(e)) != null) {
|
||
return t[1].split(',').map(function (e, t) {
|
||
return t === 3
|
||
? Math.floor(255 * parseFloat(e.trim()))
|
||
: Math.min(255, parseInt(e.trim()));
|
||
});
|
||
}
|
||
var i = e.toLowerCase();
|
||
if (shared.hasOwn(EXTENDED_COLOR_KEYWORDS, i)) {
|
||
t = /^#([0-9|A-F|a-f]{6,8})$/.exec(EXTENDED_COLOR_KEYWORDS[i]);
|
||
const n = parseInt(t[1].slice(0, 2), 16);
|
||
const o = parseInt(t[1].slice(2, 4), 16);
|
||
const r = parseInt(t[1].slice(4, 6), 16);
|
||
let a = parseInt(t[1].slice(6, 8), 16);
|
||
a = a >= 0 ? a : 255;
|
||
return [n, o, r, a];
|
||
}
|
||
console.error('unsupported color:' + e);
|
||
return [0, 0, 0, 255];
|
||
}
|
||
/**
|
||
* css value 分割多值,兼容包含括号的 css 方法,比如 var/env/calc() 等
|
||
*/
|
||
function splitValues(value) {
|
||
const trimmedValue = value.trim();
|
||
if (!trimmedValue.includes('(')) {
|
||
return trimmedValue.split(/\s+/);
|
||
}
|
||
const parts = [];
|
||
let current = '';
|
||
let depth = 0;
|
||
for (let i = 0; i < trimmedValue.length; i++) {
|
||
const char = trimmedValue[i];
|
||
if (char === '(') {
|
||
depth++;
|
||
current += char;
|
||
}
|
||
else if (char === ')') {
|
||
if (depth > 0) {
|
||
depth--;
|
||
}
|
||
current += char;
|
||
}
|
||
else if (/\s/.test(char)) {
|
||
if (depth === 0) {
|
||
if (current) {
|
||
parts.push(current);
|
||
current = '';
|
||
}
|
||
}
|
||
else {
|
||
// 多个空格处理一个
|
||
if (current.length > 0 && !/\s$/.test(current)) {
|
||
current += ' ';
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
current += char;
|
||
}
|
||
}
|
||
if (current) {
|
||
parts.push(current);
|
||
}
|
||
return parts;
|
||
}
|
||
|
||
const backgroundColor = 'background-color' ;
|
||
const backgroundImage = 'background-image' ;
|
||
const handleTransformBackground = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
if (value === 'none') {
|
||
return [
|
||
createDecl(backgroundImage, 'none', important, raws, source),
|
||
createDecl(backgroundColor, 'transparent', important, raws, source),
|
||
];
|
||
}
|
||
if (/^#?\S+$/.test(value) || /^rgba?(.+)$/.test(value)) {
|
||
return [
|
||
createDecl(backgroundImage, 'none', important, raws, source),
|
||
createDecl(backgroundColor, value, important, raws, source),
|
||
];
|
||
}
|
||
else if (/^linear-gradient(.+)$/.test(value)) {
|
||
return [
|
||
createDecl(backgroundImage, value, important, raws, source),
|
||
createDecl(backgroundColor, 'transparent', important, raws, source),
|
||
];
|
||
}
|
||
else if (value == '') {
|
||
return [
|
||
createDecl(backgroundImage, 'none', important, raws, source),
|
||
createDecl(backgroundColor, 'transparent', important, raws, source),
|
||
];
|
||
}
|
||
return [decl];
|
||
};
|
||
const handleTransformBackgroundNvue = (decl) => {
|
||
const { value, important, raws, source } = decl;
|
||
if (/^#?\S+$/.test(value) || /^rgba?(.+)$/.test(value)) {
|
||
return [createDecl(backgroundColor, value, important, raws, source)];
|
||
}
|
||
else if (/^linear-gradient(.+)$/.test(value)) {
|
||
return [createDecl(backgroundImage, value, important, raws, source)];
|
||
}
|
||
else if (value == '') {
|
||
return [decl];
|
||
}
|
||
return [decl];
|
||
};
|
||
function createTransformBackground(options) {
|
||
return (decl) => {
|
||
// nvue 平台维持原有逻辑不变
|
||
const isUvuePlatform = options.type === 'uvue';
|
||
if (isUvuePlatform) {
|
||
return handleTransformBackground(decl);
|
||
}
|
||
else {
|
||
return handleTransformBackgroundNvue(decl);
|
||
}
|
||
};
|
||
}
|
||
|
||
const borderTop = 'border-top-' ;
|
||
const borderRight = 'border-right-' ;
|
||
const borderBottom = 'border-bottom-' ;
|
||
const borderLeft = 'border-left-' ;
|
||
const transformBorderColor = (decl) => {
|
||
let { prop, value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const _property_split = shared.hyphenate(prop).split('-');
|
||
let property = _property_split[_property_split.length - 1];
|
||
const splitResult = splitValues(value); // 1pt
|
||
switch (splitResult.length) {
|
||
case 1:
|
||
if (_property_split.length === 3) {
|
||
// border-top-width
|
||
return [decl];
|
||
}
|
||
// border-width
|
||
splitResult.push(splitResult[0], splitResult[0], splitResult[0]);
|
||
break;
|
||
case 2:
|
||
splitResult.push(splitResult[0], splitResult[1]);
|
||
break;
|
||
case 3:
|
||
splitResult.push(splitResult[1]);
|
||
break;
|
||
}
|
||
return [
|
||
createDecl(borderTop + property, splitResult[0], important, raws, source),
|
||
createDecl(borderRight + property, splitResult[1], important, raws, source),
|
||
createDecl(borderBottom + property, splitResult[2], important, raws, source),
|
||
createDecl(borderLeft + property, splitResult[3], important, raws, source),
|
||
];
|
||
};
|
||
const transformBorderColorNvue = (decl) => {
|
||
let { prop, value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
let property = shared.hyphenate(prop).split('-')[1];
|
||
const splitResult = value.replace(/\s*,\s*/g, ',').split(/\s+/);
|
||
switch (splitResult.length) {
|
||
case 1:
|
||
return [decl];
|
||
case 2:
|
||
splitResult.push(splitResult[0], splitResult[1]);
|
||
break;
|
||
case 3:
|
||
splitResult.push(splitResult[1]);
|
||
break;
|
||
}
|
||
return [
|
||
createDecl(borderTop + property, splitResult[0], important, raws, source),
|
||
createDecl(borderRight + property, splitResult[1], important, raws, source),
|
||
createDecl(borderBottom + property, splitResult[2], important, raws, source),
|
||
createDecl(borderLeft + property, splitResult[3], important, raws, source),
|
||
];
|
||
};
|
||
|
||
const transformBorderStyle = transformBorderColor;
|
||
const transformBorderStyleNvue = transformBorderColorNvue;
|
||
|
||
const transformBorderWidth = transformBorderColor;
|
||
const transformBorderWidthNvue = transformBorderColorNvue;
|
||
|
||
function isSingleCssVarValue(value) {
|
||
const trimmedValue = value.trim();
|
||
if (splitValues(trimmedValue).length !== 1 || !/^var\(/i.test(trimmedValue)) {
|
||
return false;
|
||
}
|
||
let depth = 0;
|
||
for (let i = 0; i < trimmedValue.length; i++) {
|
||
const char = trimmedValue[i];
|
||
if (char === '(') {
|
||
depth++;
|
||
}
|
||
else if (char === ')') {
|
||
if (depth === 0) {
|
||
return false;
|
||
}
|
||
depth--;
|
||
if (depth === 0 && trimmedValue.slice(i + 1).trim()) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
return depth === 0;
|
||
}
|
||
function tryExpandSingleValueVarShorthand(decl, props, value) {
|
||
// 当整个简写值只有一个 var() 时,无法静态判断它属于哪个子属性,
|
||
// 这里直接复制到每个长属性,交给运行时再解析。
|
||
if (!isSingleCssVarValue(value)) {
|
||
return null;
|
||
}
|
||
const { important, raws, source } = decl;
|
||
return props.map((prop) => createDecl(prop, value, important, raws, source));
|
||
}
|
||
|
||
const borderWidth = '-width' ;
|
||
const borderStyle = '-style' ;
|
||
const borderColor = '-color' ;
|
||
const BORDER_WIDTH_REGEXP = /^(?:[\d.]+\S*|thin|medium|thick)$/;
|
||
// 这里按完整 CSS line-style 识别,后续再交给 border-*-style 的既有校验逻辑报精确错误。
|
||
const BORDER_STYLE_REGEXP = /^(?:none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset)$/;
|
||
const BORDER_SHORTHAND_VAR_ORDER_WARNING = '__borderShorthandVarOrderWarning';
|
||
function createBorderVarOrderWarning(prop, value) {
|
||
return supportedValueWithTipsReason(prop, value, '(border shorthand with CSS variables must follow `width style color`, for example: `1px solid var(--color, #999999)`)');
|
||
}
|
||
function isCssVarValue(value) {
|
||
return value.startsWith('var(');
|
||
}
|
||
function isBorderWidthValue(value) {
|
||
return isCssVarValue(value) || BORDER_WIDTH_REGEXP.test(value);
|
||
}
|
||
function isBorderStyleValue(value) {
|
||
return isCssVarValue(value) || BORDER_STYLE_REGEXP.test(value);
|
||
}
|
||
function isBorderColorValue(value) {
|
||
return (isCssVarValue(value) ||
|
||
(!BORDER_WIDTH_REGEXP.test(value) && !BORDER_STYLE_REGEXP.test(value)));
|
||
}
|
||
function createTransformBorder(options) {
|
||
return (decl) => {
|
||
const { prop, value, important, raws, source } = decl;
|
||
const singleVarResult = tryExpandSingleValueVarShorthand(decl, [prop + borderWidth, prop + borderStyle, prop + borderColor], value);
|
||
// 单个 var() 无法提前判断是 width/style/color,dom2 下先平铺后继续展开。
|
||
if (singleVarResult) {
|
||
return [
|
||
...transformBorderWidth(singleVarResult[0]),
|
||
...transformBorderStyle(singleVarResult[1]),
|
||
...transformBorderColor(singleVarResult[2]),
|
||
];
|
||
}
|
||
let splitResult = splitValues(value);
|
||
const havVar = splitResult.some((str) => str.startsWith('var('));
|
||
let result = [];
|
||
// 包含 var 时按位置解析,避免把 style 误判成 color
|
||
if (havVar) {
|
||
if (splitResult.length > 3 ||
|
||
(splitResult.length === 3 &&
|
||
(!isBorderWidthValue(splitResult[0]) ||
|
||
!isBorderStyleValue(splitResult[1]) ||
|
||
!isBorderColorValue(splitResult[2])))) {
|
||
decl[BORDER_SHORTHAND_VAR_ORDER_WARNING] =
|
||
createBorderVarOrderWarning(prop, value);
|
||
return [];
|
||
}
|
||
result = splitResult;
|
||
splitResult = [];
|
||
}
|
||
else {
|
||
result = [BORDER_WIDTH_REGEXP, BORDER_STYLE_REGEXP, /\S+/].map((item) => {
|
||
const index = splitResult.findIndex((str) => item.test(str));
|
||
return index < 0 ? null : splitResult.splice(index, 1)[0];
|
||
});
|
||
}
|
||
if (splitResult.length > 0 && value != '') {
|
||
return [decl];
|
||
}
|
||
const defaultWidth = (str) => {
|
||
if (str != null) {
|
||
return str.trim();
|
||
}
|
||
return 'medium';
|
||
};
|
||
const defaultStyle = (str) => {
|
||
if (str != null) {
|
||
return str.trim();
|
||
}
|
||
return 'none';
|
||
};
|
||
const defaultColor = (str) => {
|
||
if (str != null) {
|
||
return str.trim();
|
||
}
|
||
return '#000000';
|
||
};
|
||
return [
|
||
...transformBorderWidth(createDecl(prop + borderWidth, defaultWidth(result[0]), important, raws, source)),
|
||
...transformBorderStyle(createDecl(prop + borderStyle, defaultStyle(result[1]), important, raws, source)),
|
||
...transformBorderColor(createDecl(prop + borderColor, defaultColor(result[2]), important, raws, source)),
|
||
];
|
||
};
|
||
}
|
||
/**
|
||
* nvue 逻辑不变
|
||
*/
|
||
function createTransformBorderNvue(options) {
|
||
return (decl) => {
|
||
let { prop, value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const splitResult = value.replace(/\s*,\s*/g, ',').split(/\s+/);
|
||
const result = [
|
||
/^[\d\.]+\S*|^(thin|medium|thick)$/,
|
||
/^(solid|dashed|dotted|none)$/,
|
||
/\S+/,
|
||
].map((item) => {
|
||
const index = splitResult.findIndex((str) => item.test(str));
|
||
return index < 0 ? null : splitResult.splice(index, 1)[0];
|
||
});
|
||
if (splitResult.length) {
|
||
return [decl];
|
||
}
|
||
return [
|
||
createDecl(prop + borderWidth, (result[0] || '0').trim(), important, raws, source),
|
||
createDecl(prop + borderStyle, (result[1] || 'solid').trim(), important, raws, source),
|
||
createDecl(prop + borderColor, (result[2] || '#000000').trim(), important, raws, source),
|
||
];
|
||
};
|
||
}
|
||
|
||
const borderTopLeftRadius = 'border-top-left-radius'
|
||
;
|
||
const borderTopRightRadius = 'border-top-right-radius'
|
||
;
|
||
const borderBottomRightRadius = 'border-bottom-right-radius'
|
||
;
|
||
const borderBottomLeftRadius = 'border-bottom-left-radius'
|
||
;
|
||
const transformBorderRadius = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const splitResult = splitValues(value);
|
||
if (value.includes('/')) {
|
||
return [decl];
|
||
}
|
||
switch (splitResult.length) {
|
||
case 1:
|
||
splitResult.push(splitResult[0], splitResult[0], splitResult[0]);
|
||
break;
|
||
case 2:
|
||
splitResult.push(splitResult[0], splitResult[1]);
|
||
break;
|
||
case 3:
|
||
splitResult.push(splitResult[1]);
|
||
break;
|
||
}
|
||
return [
|
||
createDecl(borderTopLeftRadius, splitResult[0], important, raws, source),
|
||
createDecl(borderTopRightRadius, splitResult[1], important, raws, source),
|
||
createDecl(borderBottomRightRadius, splitResult[2], important, raws, source),
|
||
createDecl(borderBottomLeftRadius, splitResult[3], important, raws, source),
|
||
];
|
||
};
|
||
const transformBorderRadiusNvue = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const splitResult = value.split(/\s+/);
|
||
if (value.includes('/')) {
|
||
return [decl];
|
||
}
|
||
// const isUvuePlatform = options.type === 'uvue'
|
||
switch (splitResult.length) {
|
||
case 1:
|
||
return [decl];
|
||
case 2:
|
||
splitResult.push(splitResult[0], splitResult[1]);
|
||
break;
|
||
case 3:
|
||
splitResult.push(splitResult[1]);
|
||
break;
|
||
}
|
||
return [
|
||
createDecl(borderTopLeftRadius, splitResult[0], important, raws, source),
|
||
createDecl(borderTopRightRadius, splitResult[1], important, raws, source),
|
||
createDecl(borderBottomRightRadius, splitResult[2], important, raws, source),
|
||
createDecl(borderBottomLeftRadius, splitResult[3], important, raws, source),
|
||
];
|
||
};
|
||
|
||
const flexDirection = 'flex-direction' ;
|
||
const flexWrap = 'flex-wrap' ;
|
||
const transformFlexFlow = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const splitResult = splitValues(value);
|
||
const singleVarResult = tryExpandSingleValueVarShorthand(decl, [flexDirection, flexWrap], value);
|
||
// 单个 var() 无法提前判断是 direction 还是 wrap,dom2 下直接平铺。
|
||
if (singleVarResult) {
|
||
return singleVarResult;
|
||
}
|
||
const result = [
|
||
/^(column|column-reverse|row|row-reverse)$/,
|
||
/^(nowrap|wrap|wrap-reverse)$/,
|
||
].map((item) => {
|
||
const index = splitResult.findIndex((str) => item.test(str));
|
||
return index < 0 ? null : splitResult.splice(index, 1)[0];
|
||
});
|
||
if (splitResult.length) {
|
||
return [decl];
|
||
}
|
||
return [
|
||
createDecl(flexDirection, result[0] || 'column', important, raws, source),
|
||
createDecl(flexWrap, result[1] || 'nowrap', important, raws, source),
|
||
];
|
||
};
|
||
|
||
const transformFont = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const result = [];
|
||
const font = parseCSSFont__default.default(value);
|
||
if (font.system) {
|
||
return result;
|
||
}
|
||
const { style, weight, size, lineHeight, family } = font;
|
||
if (style) {
|
||
result.push(createDecl('font-style', style, important, raws, source));
|
||
}
|
||
if (weight) {
|
||
result.push(createDecl('font-weight', weight, important, raws, source));
|
||
}
|
||
if (size) {
|
||
result.push(createDecl('font-size', size, important, raws, source));
|
||
}
|
||
if (lineHeight) {
|
||
result.push(createDecl('line-height', lineHeight, important, raws, source));
|
||
}
|
||
if (family) {
|
||
result.push(createDecl('font-family', serialize(family), important, raws, source));
|
||
}
|
||
return result;
|
||
};
|
||
function serialize(family) {
|
||
return family.map((f) => (f.includes(' ') ? `"${f}"` : f)).join(', ');
|
||
}
|
||
|
||
const top = '-top' ;
|
||
const right = '-right' ;
|
||
const bottom = '-bottom' ;
|
||
const left = '-left' ;
|
||
const createTransformBox = (type) => {
|
||
return (decl) => {
|
||
const { value, important, raws, source } = decl;
|
||
const splitResult = splitValues(value);
|
||
switch (splitResult.length) {
|
||
case 1:
|
||
splitResult.push(splitResult[0], splitResult[0], splitResult[0]);
|
||
break;
|
||
case 2:
|
||
splitResult.push(splitResult[0], splitResult[1]);
|
||
break;
|
||
case 3:
|
||
splitResult.push(splitResult[1]);
|
||
break;
|
||
}
|
||
return [
|
||
createDecl(type + top, splitResult[0], important, raws, source),
|
||
createDecl(type + right, splitResult[1], important, raws, source),
|
||
createDecl(type + bottom, splitResult[2], important, raws, source),
|
||
createDecl(type + left, splitResult[3], important, raws, source),
|
||
];
|
||
};
|
||
};
|
||
const transformMargin = createTransformBox('margin');
|
||
|
||
const transformPadding = createTransformBox('padding');
|
||
|
||
const transitionProperty = 'transition-property'
|
||
;
|
||
const transitionDuration = 'transition-duration'
|
||
;
|
||
const transitionTimingFunction = 'transition-timing-function'
|
||
;
|
||
const transitionDelay = 'transition-delay' ;
|
||
const transformTransition = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const result = [];
|
||
let match;
|
||
// 针对 cubic-bezier 特殊处理
|
||
// eg: cubic-bezier(0.42, 0, 1.0, 3) // (0.2,-2,0.8,2)
|
||
if (value.includes('cubic-bezier')) {
|
||
const CHUNK_REGEXP = /^(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?\s*((\S*)|cubic-bezier\(.*\))?\s*(\d*\.?\d+(?:ms|s)?)?$/;
|
||
match = value.match(CHUNK_REGEXP);
|
||
}
|
||
else {
|
||
const CHUNK_REGEXP = /^(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?\s*(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?$/;
|
||
match = value.match(CHUNK_REGEXP);
|
||
}
|
||
if (!match) {
|
||
return result;
|
||
}
|
||
match[1] &&
|
||
result.push(createDecl(transitionProperty, match[1], important, raws, source));
|
||
match[2] &&
|
||
result.push(createDecl(transitionDuration, match[2], important, raws, source));
|
||
match[3] &&
|
||
result.push(createDecl(transitionTimingFunction, match[3], important, raws, source));
|
||
match[4] &&
|
||
result.push(createDecl(transitionDelay, match[4], important, raws, source));
|
||
return result;
|
||
};
|
||
|
||
const flexGrow = 'flex-grow' ;
|
||
const flexShrink = 'flex-shrink' ;
|
||
const flexBasis = 'flex-basis' ;
|
||
const transformFlex = (decl) => {
|
||
let { value, important, raws, source } = decl;
|
||
value = value.trim();
|
||
const result = [];
|
||
const splitResult = splitValues(value);
|
||
const singleVarResult = tryExpandSingleValueVarShorthand(decl, [flexGrow, flexShrink, flexBasis], value);
|
||
// 单个 var() 无法提前拆出 grow/shrink/basis,dom2 下按 border 的兜底逻辑平铺。
|
||
if (singleVarResult) {
|
||
return singleVarResult;
|
||
}
|
||
// 是否 flex-grow 的有效值 <number [0,∞]>
|
||
const isFlexGrowValid = (v) => isNumber(Number(v)) && !Number.isNaN(Number(v));
|
||
const isFlexShrinkValid = (v) => isNumber(Number(v)) && !Number.isNaN(Number(v)) && Number(v) >= 0;
|
||
const isFlexBasisValid = (v) => typeof v === 'string' && v.trim() !== '';
|
||
if (splitResult.length === 1) {
|
||
// 关键字处理
|
||
if (value === 'none') {
|
||
result.push(createDecl(flexGrow, '0', important, raws, source), createDecl(flexShrink, '0', important, raws, source), createDecl(flexBasis, 'auto', important, raws, source));
|
||
return result;
|
||
}
|
||
if (value === 'auto') {
|
||
result.push(createDecl(flexGrow, '1', important, raws, source), createDecl(flexShrink, '1', important, raws, source), createDecl(flexBasis, 'auto', important, raws, source));
|
||
return result;
|
||
}
|
||
if (value === 'initial') {
|
||
result.push(createDecl(flexGrow, '0', important, raws, source), createDecl(flexShrink, '1', important, raws, source), createDecl(flexBasis, 'auto', important, raws, source));
|
||
return result;
|
||
}
|
||
const v = splitResult[0];
|
||
// number 视为 flex-grow
|
||
if (isFlexGrowValid(v)) {
|
||
if (Number(v) < 0) {
|
||
return [];
|
||
}
|
||
result.push(createDecl(flexGrow, v, important, raws, source), createDecl(flexShrink, '1', important, raws, source), createDecl(flexBasis, '0%', important, raws, source));
|
||
return result;
|
||
}
|
||
else if (isFlexBasisValid(v)) {
|
||
result.push(createDecl(flexGrow, '1', important, raws, source), createDecl(flexShrink, '1', important, raws, source), createDecl(flexBasis, v, important, raws, source));
|
||
return result;
|
||
}
|
||
else {
|
||
return [decl];
|
||
}
|
||
}
|
||
else if (splitResult.length === 2) {
|
||
const [v1, v2] = splitResult;
|
||
if (isFlexGrowValid(v1)) {
|
||
if (isFlexShrinkValid(v2)) {
|
||
// flex: 1 2 => 1 2 0%
|
||
result.push(createDecl(flexGrow, v1, important, raws, source), createDecl(flexShrink, v2, important, raws, source), createDecl(flexBasis, '0%', important, raws, source));
|
||
return result;
|
||
}
|
||
else {
|
||
// flex: 1 100px => 1 1 100px
|
||
result.push(createDecl(flexGrow, v1, important, raws, source), createDecl(flexShrink, '1', important, raws, source), createDecl(flexBasis, v2, important, raws, source));
|
||
return result;
|
||
}
|
||
}
|
||
else {
|
||
return [decl];
|
||
}
|
||
}
|
||
else if (splitResult.length === 3) {
|
||
const [v1, v2, v3] = splitResult;
|
||
if (isFlexGrowValid(v1) && isFlexShrinkValid(v2)) {
|
||
result.push(createDecl(flexGrow, v1, important, raws, source), createDecl(flexShrink, v2, important, raws, source), createDecl(flexBasis, v3, important, raws, source));
|
||
return result;
|
||
}
|
||
else {
|
||
// fallback
|
||
return [decl];
|
||
}
|
||
}
|
||
// 其它情况,原样返回
|
||
return [decl];
|
||
};
|
||
|
||
function getDeclTransforms(options) {
|
||
const transformBorder = options.type === 'uvue'
|
||
? createTransformBorder()
|
||
: createTransformBorderNvue();
|
||
const styleMap = {
|
||
transition: transformTransition,
|
||
border: transformBorder,
|
||
background: createTransformBackground(options),
|
||
['borderTop']: transformBorder,
|
||
['borderRight']: transformBorder,
|
||
['borderBottom']: transformBorder,
|
||
['borderLeft']: transformBorder,
|
||
['borderStyle']: options.type === 'uvue' ? transformBorderStyle : transformBorderStyleNvue,
|
||
['borderWidth']: options.type === 'uvue' ? transformBorderWidth : transformBorderWidthNvue,
|
||
['borderColor']: options.type === 'uvue' ? transformBorderColor : transformBorderColorNvue,
|
||
['borderRadius']: options.type === 'uvue'
|
||
? transformBorderRadius
|
||
: transformBorderRadiusNvue,
|
||
// uvue已经支持这些简写属性,不需要展开
|
||
// margin,padding继续展开,确保样式的优先级
|
||
margin: transformMargin,
|
||
padding: transformPadding,
|
||
['flexFlow']: transformFlexFlow,
|
||
};
|
||
if (options.type === 'uvue') {
|
||
styleMap.flex = transformFlex;
|
||
}
|
||
let result = {};
|
||
{
|
||
styleMap.font = transformFont;
|
||
for (const property in styleMap) {
|
||
result[hyphenateStyleProperty(property)] = styleMap[property];
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
let DeclTransforms;
|
||
const expanded = Symbol('expanded');
|
||
function expand(options) {
|
||
const plugin = {
|
||
postcssPlugin: `${options.type || 'nvue'}:expand`,
|
||
Declaration(decl, helper) {
|
||
if (decl[expanded]) {
|
||
return;
|
||
}
|
||
if (!DeclTransforms) {
|
||
DeclTransforms = getDeclTransforms(options);
|
||
}
|
||
const transform = DeclTransforms[decl.prop];
|
||
if (transform) {
|
||
const res = transform(decl);
|
||
const reason = decl[BORDER_SHORTHAND_VAR_ORDER_WARNING];
|
||
if (reason && helper && decl.warn) {
|
||
let needLog = false;
|
||
if (options.logLevel === 'NOTE') {
|
||
needLog = true;
|
||
}
|
||
else if (options.logLevel === 'ERROR') {
|
||
if (reason.startsWith('ERROR:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
else {
|
||
if (!reason.startsWith('NOTE:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
if (needLog) {
|
||
decl.warn(helper.result, reason);
|
||
}
|
||
delete decl[BORDER_SHORTHAND_VAR_ORDER_WARNING];
|
||
}
|
||
const isSame = res.length === 1 && res[0] === decl;
|
||
if (!isSame) {
|
||
decl.replaceWith(res);
|
||
}
|
||
}
|
||
decl[expanded] = true;
|
||
},
|
||
};
|
||
return plugin;
|
||
}
|
||
function vueStyleValidator(options) {
|
||
const plugin = {
|
||
postcssPlugin: `${options.type || 'nvue'}:vue-style-validator`,
|
||
Declaration(decl, helper) {
|
||
const isUVue = options.type === 'uvue';
|
||
if (!isUVue) {
|
||
return;
|
||
}
|
||
if (decl.prop.startsWith('--')) {
|
||
const parent = decl.parent;
|
||
if (parent?.type === 'root' &&
|
||
(parent?.source?.input?.from.includes('&type=style') ||
|
||
parent?.source?.input?.from.endsWith('uvue.style.uts'))) {
|
||
// 命中:在根节点且不是 template 样式,需要禁止
|
||
const reason = `ERROR: CSS custom properties must be inside a CSS rule (selector) or @ rule. Found "${decl.prop}" at top level in Vue style block.`;
|
||
let needLog = false;
|
||
if (options.logLevel === 'NOTE') {
|
||
needLog = true;
|
||
}
|
||
else if (options.logLevel === 'ERROR') {
|
||
if (reason.startsWith('ERROR:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
else {
|
||
if (!reason.startsWith('NOTE:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
if (needLog && helper && decl.warn) {
|
||
decl.warn(helper.result, reason);
|
||
}
|
||
// 移除这个声明
|
||
decl.remove();
|
||
}
|
||
}
|
||
},
|
||
};
|
||
return plugin;
|
||
}
|
||
|
||
const normalizeColor = (v) => {
|
||
v = (v || '').toString();
|
||
if (v.match(/^#[0-9a-fA-F]{6}$/)) {
|
||
return { value: v };
|
||
}
|
||
// rgba issues 13371
|
||
if (v.match(/^#[0-9a-fA-F]{4}$/)) {
|
||
return {
|
||
value: '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3] + v[4] + v[4],
|
||
reason: function reason(k, v, result) {
|
||
return autofixedReason(v, result);
|
||
},
|
||
};
|
||
}
|
||
if (v.match(/^#[0-9a-fA-F]{8}$/)) {
|
||
return {
|
||
value: v,
|
||
};
|
||
}
|
||
if (v.match(/^#[0-9a-fA-F]{3}$/)) {
|
||
return {
|
||
value: '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3],
|
||
reason: function reason(k, v, result) {
|
||
return autofixedReason(v, result);
|
||
},
|
||
};
|
||
}
|
||
if (EXTENDED_COLOR_KEYWORDS[v]) {
|
||
return {
|
||
value: EXTENDED_COLOR_KEYWORDS[v],
|
||
reason: function reason(k, v, result) {
|
||
return autofixedReason(v, result);
|
||
},
|
||
};
|
||
}
|
||
let arrColor, r, g, b, a;
|
||
const RGB_REGEXP = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/gi;
|
||
const RGBA_REGEXP = /^rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d*\.?\d+)\s*\)$/gi;
|
||
if ((arrColor = RGB_REGEXP.exec(v))) {
|
||
r = parseInt(arrColor[1]);
|
||
g = parseInt(arrColor[2]);
|
||
b = parseInt(arrColor[3]);
|
||
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
|
||
return { value: 'rgb(' + [r, g, b].join(',') + ')' };
|
||
}
|
||
}
|
||
if ((arrColor = RGBA_REGEXP.exec(v))) {
|
||
r = parseInt(arrColor[1]);
|
||
g = parseInt(arrColor[2]);
|
||
b = parseInt(arrColor[3]);
|
||
a = parseFloat(arrColor[4]);
|
||
if (r >= 0 &&
|
||
r <= 255 &&
|
||
g >= 0 &&
|
||
g <= 255 &&
|
||
b >= 0 &&
|
||
b <= 255 &&
|
||
a >= 0 &&
|
||
a <= 1) {
|
||
return { value: 'rgba(' + [r, g, b, a].join(',') + ')' };
|
||
}
|
||
}
|
||
if (v === 'transparent') {
|
||
return { value: 'rgba(0,0,0,0)' };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return validReason(k, v);
|
||
},
|
||
};
|
||
};
|
||
|
||
function createEnumNormalize(items) {
|
||
return (v) => {
|
||
const index = items.indexOf(v);
|
||
if (index > 0) {
|
||
return { value: v };
|
||
}
|
||
if (index === 0) {
|
||
return {
|
||
value: v,
|
||
reason: function reason(k, v, result) {
|
||
return defaultValueReason(k, v);
|
||
},
|
||
};
|
||
}
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, items);
|
||
},
|
||
};
|
||
};
|
||
}
|
||
function createEnumNormalizeWithPlatform(items) {
|
||
return (v, { platform }) => {
|
||
const property = items.find((item) => item.name === v);
|
||
const supportedEnum = items
|
||
.filter((item) => {
|
||
const supportedPlatforms = getSupportedPlatforms(item.uniPlatform);
|
||
return supportedPlatforms.includes(platform);
|
||
})
|
||
.map((item) => item.name);
|
||
if (property) {
|
||
const supportedPlatforms = getSupportedPlatforms(property.uniPlatform);
|
||
// TODO 未跨平台支持的属性特殊提示
|
||
if (!supportedPlatforms.includes(platform)) {
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, supportedEnum);
|
||
},
|
||
};
|
||
}
|
||
return { value: v };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, supportedEnum);
|
||
},
|
||
};
|
||
};
|
||
}
|
||
|
||
const normalizeFlexWrap = (v) => {
|
||
const values = ['nowrap', 'wrap', 'wrap-reverse'];
|
||
const index = values.indexOf(v);
|
||
if (index > 0) {
|
||
return {
|
||
value: v,
|
||
reason(k, v, result) {
|
||
return compatibilityReason(k);
|
||
},
|
||
};
|
||
}
|
||
if (index === 0) {
|
||
return {
|
||
value: v,
|
||
reason: function reason(k, v, result) {
|
||
return defaultValueReason(k, v);
|
||
},
|
||
};
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedEnumReason(k, v, values);
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeInteger = (v) => {
|
||
v = (v || '').toString();
|
||
if (v.match(/^[-+]?\d+$/)) {
|
||
return { value: parseInt(v, 10) };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['integer']);
|
||
},
|
||
};
|
||
};
|
||
|
||
function createNormalizeLength({ removePx, property, } = {}) {
|
||
return (v, options) => {
|
||
v = (v || '').toString();
|
||
if (options.type === 'uvue' &&
|
||
!v.includes('calc(') &&
|
||
((/var\([^)]+\)/.test(v) &&
|
||
(/--uni-safe-area-inset-(top|bottom|left|right)/.test(v) ||
|
||
/--status-bar-height/.test(v))) ||
|
||
/--window-(top|bottom)/.test(v) ||
|
||
/env\(([^)]+)\)/.test(v))) {
|
||
v = v.replace(/\s/g, '');
|
||
return { value: v };
|
||
}
|
||
const match = v.match(LENGTH_REGEXP);
|
||
if (match) {
|
||
var unit = match[1];
|
||
const uvue = options.type === 'uvue';
|
||
if (uvue) {
|
||
if (!unit || (unit === 'px' && removePx)) {
|
||
return { value: parseFloat(v) };
|
||
}
|
||
else if (unit === 'px' ||
|
||
unit === 'rpx' ||
|
||
// 只有line-height支持em单位
|
||
(unit === 'em' && property === 'line-height')) {
|
||
return { value: v };
|
||
}
|
||
}
|
||
else {
|
||
// nvue
|
||
if (!unit || unit === 'px') {
|
||
return { value: parseFloat(v) };
|
||
}
|
||
if (SUPPORT_CSS_UNIT.includes(unit)) {
|
||
return { value: v };
|
||
}
|
||
else {
|
||
return {
|
||
value: parseFloat(v),
|
||
reason(k, v, result) {
|
||
return supportedUnitWithAutofixedReason(unit, v, result);
|
||
},
|
||
};
|
||
}
|
||
}
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['number', 'pixel']);
|
||
},
|
||
};
|
||
};
|
||
}
|
||
const normalizeLength = createNormalizeLength({
|
||
removePx: true,
|
||
});
|
||
const normalizeLengthWithOptions = createNormalizeLength;
|
||
const normalizePercent = (v) => {
|
||
v = (v || '').toString();
|
||
const match = v.match(LENGTH_REGEXP);
|
||
if (match) {
|
||
var unit = match[1];
|
||
if (unit === '%') {
|
||
return { value: v };
|
||
}
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['percent']);
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeNumber = (v) => {
|
||
v = (v || '').toString();
|
||
var match = v.match(LENGTH_REGEXP);
|
||
if (match && !match[1]) {
|
||
return { value: parseFloat(v) };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['number']);
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeString = (v) => {
|
||
v = (v || '').toString().replace(/["']/g, '');
|
||
return {
|
||
value: v,
|
||
};
|
||
};
|
||
|
||
const normalizeShorthandLength = (v, options) => {
|
||
v = (v || '').toString();
|
||
let value = [];
|
||
let reason = [];
|
||
const results = v.split(/\s+/).map((v) => normalizeLength(v, options));
|
||
for (let i = 0; i < results.length; ++i) {
|
||
const res = results[i];
|
||
if (res.value === null) {
|
||
return res;
|
||
}
|
||
value.push(res.value);
|
||
reason.push(res.reason);
|
||
}
|
||
return {
|
||
value: value.join(' '),
|
||
reason: function (k, v, result) {
|
||
return reason
|
||
.map(function (res) {
|
||
if (shared.isFunction(res)) {
|
||
return res(k, v, result);
|
||
}
|
||
})
|
||
.join('\n');
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeTransform = (v) => {
|
||
return { value: v };
|
||
};
|
||
|
||
const normalizeInterval = (v, options) => {
|
||
v = (v || 0).toString();
|
||
let match, num;
|
||
if ((match = v.match(/^\d*\.?\d+(ms|s)?$/))) {
|
||
const uvue = options.type === 'uvue';
|
||
if (uvue) {
|
||
// uvue 需要单位
|
||
if (match[1]) {
|
||
return { value: v };
|
||
}
|
||
}
|
||
else {
|
||
num = parseFloat(match[0]);
|
||
if (!match[1]) {
|
||
return { value: parseInt(num + '') };
|
||
}
|
||
if (match[1] === 's') {
|
||
num *= 1000;
|
||
}
|
||
return {
|
||
value: parseInt(num + ''),
|
||
reason(k, v, result) {
|
||
return autofixedReason(v, result);
|
||
},
|
||
};
|
||
}
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['number of seconds', 'milliseconds']);
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeTimingFunction = (v) => {
|
||
v = (v || '').toString();
|
||
if (v.match(/^linear|ease|ease-in|ease-out|ease-in-out$/)) {
|
||
return { value: v };
|
||
}
|
||
let match;
|
||
if ((match = v.match(/^cubic-bezier\(\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*,\s*(.*)\s*\)$/))) {
|
||
if (match[1].match(NUM_REGEXP) &&
|
||
match[2].match(NUM_REGEXP) &&
|
||
match[3].match(NUM_REGEXP) &&
|
||
match[4].match(NUM_REGEXP)) {
|
||
const ret = [
|
||
parseFloat(match[1]),
|
||
parseFloat(match[2]),
|
||
parseFloat(match[3]),
|
||
parseFloat(match[4]),
|
||
].join(',');
|
||
return { value: 'cubic-bezier(' + ret + ')' };
|
||
}
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedEnumReason(k, v, [
|
||
'linear',
|
||
'ease',
|
||
'ease-in',
|
||
'ease-out',
|
||
'ease-in-out',
|
||
'cubic-bezier(n,n,n,n)',
|
||
]);
|
||
},
|
||
};
|
||
};
|
||
|
||
function createCombinedNormalize(normalizes) {
|
||
return (v, options) => {
|
||
const reasons = [];
|
||
for (let i = 0; i < normalizes.length; i++) {
|
||
const result = normalizes[i](v, options);
|
||
if (result.value !== null) {
|
||
return result;
|
||
}
|
||
if (result.reason) {
|
||
reasons.push(result.reason);
|
||
}
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return normalizeReasons(reasons.map((reason) => reason(k, v, result)), k, v).join('\n');
|
||
},
|
||
};
|
||
};
|
||
}
|
||
|
||
const normalizeGradient = (v) => {
|
||
v = (v || '').toString();
|
||
if (/^linear-gradient(.+)$/s.test(v)) {
|
||
return { value: v };
|
||
}
|
||
return {
|
||
// 枚举里会做reason提示
|
||
value: null,
|
||
};
|
||
};
|
||
const normalizeUrl = (v) => {
|
||
v = (v || '').toString();
|
||
if (/^url(.+)$/s.test(v)) {
|
||
return { value: v };
|
||
}
|
||
return {
|
||
value: null,
|
||
};
|
||
};
|
||
|
||
function normalizePlatform(normalize, uniPlatform) {
|
||
return (v, options, declInfo) => {
|
||
// platform 未定义时候忽略
|
||
const currentPlatform = options.platform;
|
||
const supportedPlatforms = getSupportedPlatforms(uniPlatform);
|
||
// TODO 未跨平台支持的属性特殊提示
|
||
if (!supportedPlatforms.includes(currentPlatform)) {
|
||
return {
|
||
value: v,
|
||
reason(k, v, result) {
|
||
return supportedPropertyReason(k);
|
||
},
|
||
};
|
||
}
|
||
return normalize(v, options, declInfo);
|
||
};
|
||
}
|
||
|
||
function normalizeShorthandProperty(normalize) {
|
||
return (v, options) => {
|
||
v = (v || '').toString();
|
||
const value = [];
|
||
const reasons = [];
|
||
const results = v.split(/\s+/).map((v) => normalize(v, options));
|
||
for (let i = 0; i < results.length; ++i) {
|
||
const res = results[i];
|
||
if (res.value === null) {
|
||
return res;
|
||
}
|
||
if (res.reason) {
|
||
reasons.push(res.reason);
|
||
}
|
||
value.push(res.value);
|
||
}
|
||
return {
|
||
value: value.length === 1 ? value[0] : value.join(' '),
|
||
reason: function (k, v, result) {
|
||
return reasons.map((reason) => reason(k, v, result)).join('\n');
|
||
},
|
||
};
|
||
};
|
||
}
|
||
|
||
function normalizeFontFace(normalize) {
|
||
return (v, options, declInfo) => {
|
||
if (declInfo?.atRule === 'font-face') {
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
const items = ['font-family', 'src'];
|
||
const name = '@' + declInfo.atRule;
|
||
return ('ERROR: property `' +
|
||
hyphenateStyleProperty(k) +
|
||
'` is not supported for `' +
|
||
name +
|
||
'` (supported properties are: `' +
|
||
items.join('`|`') +
|
||
'`)');
|
||
},
|
||
};
|
||
}
|
||
return normalize(v, options, declInfo);
|
||
};
|
||
}
|
||
// 只有@font-face下的src属性才支持
|
||
const normalizeSrc = (v, options, declInfo) => {
|
||
if (declInfo?.atRule === 'font-face') {
|
||
return { value: v };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason(k, v, result) {
|
||
return supportedPropertyReason(k);
|
||
},
|
||
};
|
||
};
|
||
|
||
const normalizeFlexFlow = (v) => {
|
||
v = (v || '').toString();
|
||
const values = v.split(/\s+/);
|
||
// flex-flow 需要定义每一个属性值
|
||
if (values.length === 1) {
|
||
return {
|
||
value: v,
|
||
reason(k, v, result) {
|
||
return supportedValueWithTipsReason(k, v, '(both property values must be explicitly defined)');
|
||
},
|
||
};
|
||
}
|
||
return {
|
||
value: v,
|
||
};
|
||
};
|
||
|
||
// transition-property 不读 css.json
|
||
// 从 property.ts 中移动到 map 里,避免循环依赖
|
||
const normalizeProperty = (v, options) => {
|
||
v = (v || '').toString();
|
||
v = v
|
||
.split(/\s*,\s*/)
|
||
.map(shared.camelize)
|
||
.join(',');
|
||
// [all, none] 是特殊值
|
||
if (options.type === 'uvue') {
|
||
if (v === 'all' || v === 'none') {
|
||
return { value: v };
|
||
}
|
||
}
|
||
if (v.split(/\s*,\s*/).every((p) => {
|
||
return !!getNormalizeMap(options)[p];
|
||
})) {
|
||
return { value: v };
|
||
}
|
||
return {
|
||
value: null,
|
||
reason: function reason(k, v, result) {
|
||
return supportedEnumReason(k, v, ['css property']);
|
||
},
|
||
};
|
||
};
|
||
const normalizeDefault = (v) => {
|
||
return { value: v };
|
||
};
|
||
const NVUE_PROP_NAME_GROUPS = {
|
||
boxModel: {
|
||
display: createEnumNormalize(['flex']),
|
||
width: normalizeLength,
|
||
height: normalizeLength,
|
||
overflow: createEnumNormalize(['hidden']),
|
||
padding: normalizeShorthandLength,
|
||
paddingLeft: normalizeLength,
|
||
paddingRight: normalizeLength,
|
||
paddingTop: normalizeLength,
|
||
paddingBottom: normalizeLength,
|
||
margin: normalizeShorthandLength,
|
||
marginLeft: normalizeLength,
|
||
marginRight: normalizeLength,
|
||
marginTop: normalizeLength,
|
||
marginBottom: normalizeLength,
|
||
borderWidth: normalizeLength,
|
||
borderLeftWidth: normalizeLength,
|
||
borderTopWidth: normalizeLength,
|
||
borderRightWidth: normalizeLength,
|
||
borderBottomWidth: normalizeLength,
|
||
borderColor: normalizeColor,
|
||
borderLeftColor: normalizeColor,
|
||
borderTopColor: normalizeColor,
|
||
borderRightColor: normalizeColor,
|
||
borderBottomColor: normalizeColor,
|
||
borderStyle: createEnumNormalize(['dotted', 'dashed', 'solid']),
|
||
borderTopStyle: createEnumNormalize(['dotted', 'dashed', 'solid']),
|
||
borderRightStyle: createEnumNormalize(['dotted', 'dashed', 'solid']),
|
||
borderBottomStyle: createEnumNormalize(['dotted', 'dashed', 'solid']),
|
||
borderLeftStyle: createEnumNormalize(['dotted', 'dashed', 'solid']),
|
||
borderRadius: normalizeLength,
|
||
borderBottomLeftRadius: normalizeLength,
|
||
borderBottomRightRadius: normalizeLength,
|
||
borderTopLeftRadius: normalizeLength,
|
||
borderTopRightRadius: normalizeLength,
|
||
},
|
||
flexbox: {
|
||
flex: normalizeNumber,
|
||
flexWrap: normalizeFlexWrap,
|
||
flexDirection: createEnumNormalize([
|
||
'column',
|
||
'row',
|
||
'column-reverse',
|
||
'row-reverse',
|
||
]),
|
||
justifyContent: createEnumNormalize([
|
||
'flex-start',
|
||
'flex-end',
|
||
'center',
|
||
'space-between',
|
||
'space-around',
|
||
]),
|
||
alignItems: createEnumNormalize([
|
||
'stretch',
|
||
'flex-start',
|
||
'flex-end',
|
||
'center',
|
||
]),
|
||
},
|
||
position: {
|
||
position: createEnumNormalize(['relative', 'absolute', 'sticky', 'fixed']),
|
||
top: normalizeLength,
|
||
bottom: normalizeLength,
|
||
left: normalizeLength,
|
||
right: normalizeLength,
|
||
zIndex: normalizeInteger,
|
||
},
|
||
common: {
|
||
opacity: normalizeNumber,
|
||
boxShadow: normalizeDefault,
|
||
backgroundColor: normalizeColor,
|
||
backgroundImage: normalizeDefault,
|
||
},
|
||
text: {
|
||
lines: normalizeInteger,
|
||
color: normalizeColor,
|
||
fontSize: normalizeLength,
|
||
fontStyle: createEnumNormalize(['normal', 'italic']),
|
||
fontFamily: normalizeDefault,
|
||
fontWeight: createEnumNormalize([
|
||
'normal',
|
||
'bold',
|
||
'100',
|
||
'200',
|
||
'300',
|
||
'400',
|
||
'500',
|
||
'600',
|
||
'700',
|
||
'800',
|
||
'900',
|
||
]),
|
||
textDecoration: createEnumNormalize(['none', 'underline', 'line-through']),
|
||
textAlign: createEnumNormalize(['left', 'center', 'right']),
|
||
textOverflow: createEnumNormalize(['clip', 'ellipsis', 'unset', 'fade']),
|
||
lineHeight: normalizeLength,
|
||
},
|
||
transition: {
|
||
transitionProperty: normalizeProperty,
|
||
transitionDuration: normalizeInterval,
|
||
transitionDelay: normalizeInterval,
|
||
transitionTimingFunction: normalizeTimingFunction,
|
||
},
|
||
transform: {
|
||
transform: normalizeTransform,
|
||
transformOrigin: normalizeTransform, // fixed by xxxxxx
|
||
},
|
||
customized: {
|
||
itemSize: normalizeLength,
|
||
itemColor: normalizeColor,
|
||
itemSelectedColor: normalizeColor,
|
||
textColor: normalizeColor,
|
||
timeColor: normalizeColor,
|
||
textHighlightColor: normalizeColor,
|
||
},
|
||
};
|
||
// 特定属性
|
||
const uvueNormalizeMap = {
|
||
transform: normalizeTransform,
|
||
fontFamily: normalizeString,
|
||
textDecoration: normalizeDefault,
|
||
boxShadow: normalizeDefault,
|
||
textShadow: normalizeDefault,
|
||
// transition-property 支持逗号多值分割
|
||
transitionProperty: normalizeProperty,
|
||
transitionTimingFunction: normalizeTimingFunction,
|
||
};
|
||
const restrictionMap = {
|
||
["length" /* Restriction.LENGTH */]: normalizeLength,
|
||
["percentage" /* Restriction.PERCENTAGE */]: normalizePercent,
|
||
["number" /* Restriction.NUMBER */]: normalizeNumber,
|
||
["number(0-1)" /* Restriction.NUMBER_0_1 */]: normalizeNumber,
|
||
["integer" /* Restriction.INTEGER */]: normalizeInteger,
|
||
["color" /* Restriction.COLOR */]: normalizeColor,
|
||
["time" /* Restriction.TIME */]: normalizeInterval,
|
||
["property" /* Restriction.PROPERTY */]: normalizeProperty,
|
||
["timing-function" /* Restriction.TIMING_FUNCTION */]: normalizeTimingFunction,
|
||
["gradient" /* Restriction.GRADIENT */]: normalizeGradient,
|
||
["url" /* Restriction.URL */]: normalizeUrl,
|
||
};
|
||
// @font-face下不支持的属性
|
||
const invalidFontFaceProperties = ['fontWeight', 'fontStyle', 'fontVariant'];
|
||
function getUVueNormalizeMap(options) {
|
||
const result = {
|
||
src: normalizeSrc,
|
||
};
|
||
let cssJson;
|
||
try {
|
||
// eslint-disable-next-line no-restricted-globals
|
||
cssJson = require('../lib/css.json');
|
||
}
|
||
catch (e) {
|
||
// 单元测试环境,源码目录
|
||
// eslint-disable-next-line no-restricted-globals
|
||
cssJson = require('../../lib/css.json');
|
||
}
|
||
const { properties } = cssJson;
|
||
for (let i = 0; i < properties.length; i++) {
|
||
const property = properties[i];
|
||
const prop = shared.camelize(property.name);
|
||
let normalize;
|
||
if (uvueNormalizeMap[prop]) {
|
||
normalize = uvueNormalizeMap[prop];
|
||
}
|
||
else {
|
||
const normalizes = getNormalizes(property, options);
|
||
if (normalizes.length > 1) {
|
||
normalize = createCombinedNormalize(normalizes);
|
||
}
|
||
else if (normalizes.length === 1) {
|
||
normalize = normalizes[0];
|
||
}
|
||
else {
|
||
normalize = normalizeDefault;
|
||
}
|
||
// 简写属性
|
||
if (property.shorthand) {
|
||
normalize = normalizeShorthandProperty(normalize);
|
||
}
|
||
// 处理@font-face下不支持的属性
|
||
if (invalidFontFaceProperties.includes(prop)) {
|
||
normalize = normalizeFontFace(normalize);
|
||
}
|
||
// 校验flexFlow属性值的个数,先临时写死,后续考虑根据css.json动态判断
|
||
if (prop === 'flexFlow') {
|
||
normalize = createCombinedNormalize([normalizeFlexFlow, normalize]);
|
||
}
|
||
}
|
||
result[prop] = normalizePlatform(normalize, property.uniPlatform);
|
||
}
|
||
return result;
|
||
}
|
||
// 读取 css.json 的 restrictions
|
||
function getNormalizes(property, options) {
|
||
const normalizes = [];
|
||
const { restrictions } = property;
|
||
restrictions.forEach((restriction) => {
|
||
let normalize = restrictionMap[restriction];
|
||
if (normalize) {
|
||
if (restriction === "length" /* Restriction.LENGTH */) {
|
||
// 如果同时有number和length,例如line-height: 1.5, line-height: 16px,则不能移除px
|
||
normalize = normalizeLengthWithOptions({
|
||
removePx: options.keepUnitPx
|
||
? false
|
||
: !restrictions.includes("number" /* Restriction.NUMBER */),
|
||
property: property.name,
|
||
});
|
||
}
|
||
normalizes.push(normalize);
|
||
}
|
||
});
|
||
// enum
|
||
if (property?.values?.length) {
|
||
normalizes.push(createEnumNormalizeWithPlatform(property.values));
|
||
}
|
||
return normalizes;
|
||
}
|
||
let normalizeMap;
|
||
function getNormalizeMap(options) {
|
||
if (normalizeMap) {
|
||
return normalizeMap;
|
||
}
|
||
const uvue = options.type === 'uvue';
|
||
if (uvue) {
|
||
normalizeMap = getUVueNormalizeMap(options);
|
||
}
|
||
else {
|
||
normalizeMap = Object.keys(NVUE_PROP_NAME_GROUPS).reduce((res, name) => {
|
||
const group = NVUE_PROP_NAME_GROUPS[name];
|
||
Object.keys(group).forEach((prop) => {
|
||
res[prop] = group[prop];
|
||
});
|
||
return res;
|
||
}, {});
|
||
}
|
||
return normalizeMap;
|
||
}
|
||
|
||
const normalized = Symbol('normalized');
|
||
function normalize(opts = {}) {
|
||
if (!shared.hasOwn(opts, 'logLevel')) {
|
||
opts.logLevel = 'WARNING';
|
||
}
|
||
const plugin = {
|
||
postcssPlugin: `${opts.type || 'nvue'}:normalize`,
|
||
Declaration: createDeclarationProcessor(opts),
|
||
};
|
||
{
|
||
plugin.Rule = createRuleProcessor(opts);
|
||
}
|
||
return plugin;
|
||
}
|
||
function createRuleProcessor(opts = {}) {
|
||
return (rule, helper) => {
|
||
if (rule[normalized]) {
|
||
return;
|
||
}
|
||
rule.selector = rule.selectors
|
||
.map((selector) => {
|
||
const isUvue = opts.type === 'uvue';
|
||
if (isUvue) {
|
||
// 特殊处理 ::v-deep 选择器 和 ::v-deep(.xxx) 写法
|
||
const hasVDeep = selector.includes('::v-deep');
|
||
const hasDeepMethod = selector.includes(':deep(');
|
||
if (hasVDeep) {
|
||
selector = selector.replace(/::v-deep/g, '');
|
||
}
|
||
if (hasDeepMethod) {
|
||
selector = selector.replace(/:deep\(([^)]+)\)/g, '$1');
|
||
}
|
||
}
|
||
// 移除组合符周围的空格,合并多个空格
|
||
selector = selector
|
||
.replace(/\s*([\+\~\>])\s*/g, '$1')
|
||
.replace(/\s+/g, ' ');
|
||
// 组合符号
|
||
if (COMBINATORS_RE.test(selector)) {
|
||
return selector;
|
||
}
|
||
let type = opts.type || 'nvue';
|
||
rule.warn(helper.result, 'ERROR: Selector `' +
|
||
selector +
|
||
'` is not supported. ' +
|
||
type +
|
||
' only support classname selector');
|
||
return '';
|
||
})
|
||
.filter(Boolean)
|
||
.join(', ');
|
||
if (!rule.selector) {
|
||
rule.remove();
|
||
}
|
||
rule[normalized] = true;
|
||
};
|
||
}
|
||
function createDeclarationProcessor(options) {
|
||
return (decl, helper) => {
|
||
if (decl[normalized]) {
|
||
return;
|
||
}
|
||
if (decl.prop.startsWith('--')) {
|
||
return;
|
||
}
|
||
else {
|
||
// @ts-expect-error
|
||
decl.__originalProp = decl.prop;
|
||
decl.prop = shared.camelize(decl.prop);
|
||
}
|
||
const { value, log } = normalizeDecl(decl, options);
|
||
if (shared.isString(value) || isNumber(value)) {
|
||
decl.value = value;
|
||
}
|
||
if (log && log.reason && helper && decl.warn) {
|
||
const { reason } = log;
|
||
let needLog = false;
|
||
if (options.logLevel === 'NOTE') {
|
||
needLog = true;
|
||
}
|
||
else if (options.logLevel === 'ERROR') {
|
||
if (reason.startsWith('ERROR:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
else {
|
||
if (!reason.startsWith('NOTE:')) {
|
||
needLog = true;
|
||
}
|
||
}
|
||
needLog && decl.warn(helper.result, reason);
|
||
}
|
||
if (value === null) {
|
||
decl.remove();
|
||
}
|
||
decl[normalized] = true;
|
||
};
|
||
}
|
||
function normalizeDecl(decl, options) {
|
||
let { prop: name, value } = decl;
|
||
let result, log;
|
||
const normalize = getNormalizeMap(options)[name];
|
||
if (options.type === 'uvue' && !value.includes('calc')) {
|
||
if (hasCssVar(value)) {
|
||
return {
|
||
value,
|
||
log,
|
||
};
|
||
}
|
||
}
|
||
if (shared.isFunction(normalize)) {
|
||
if (!shared.isFunction(value)) {
|
||
result = normalize(value, options, {
|
||
atRule: decl.parent?.type === 'atrule' ? decl.parent.name : '',
|
||
});
|
||
}
|
||
else {
|
||
result = { value: value };
|
||
}
|
||
if (result.reason) {
|
||
log = { reason: result.reason(name, value, result.value) };
|
||
}
|
||
}
|
||
else {
|
||
// ensure number type, no `px`
|
||
if (shared.isString(value)) {
|
||
const match = value.match(LENGTH_REGEXP);
|
||
if (match && (!match[1] || SUPPORT_CSS_UNIT.indexOf(match[1]) === -1)) {
|
||
value = parseFloat(value);
|
||
}
|
||
}
|
||
result = { value: value };
|
||
log = {
|
||
reason: supportedPropertyReason(name),
|
||
};
|
||
}
|
||
return {
|
||
value: result.value,
|
||
log,
|
||
};
|
||
}
|
||
function hasCssVar(value) {
|
||
return value.includes('var(');
|
||
}
|
||
|
||
function objectifier(node, options) {
|
||
return objectifierWithMessages(node, options).obj;
|
||
}
|
||
function objectifierWithMessages(node, options) {
|
||
if (!node) {
|
||
return { obj: {}, messages: [] };
|
||
}
|
||
const parseMessages = options.parseMessages || [];
|
||
function isInParseMessages(node) {
|
||
return parseMessages.some((message) => message.node.source === node.source);
|
||
}
|
||
const context = {
|
||
'FONT-FACE': [],
|
||
TRANSITION: {},
|
||
messages: [],
|
||
warn(node, message) {
|
||
// 如果parse阶段已经有该节点的错误,则不再添加
|
||
if (!isInParseMessages(node)) {
|
||
context.messages.push(new postcss.Warning(message, { node }));
|
||
}
|
||
},
|
||
};
|
||
const result = transform(node, context, options);
|
||
if (options.trim) {
|
||
trimObj(result);
|
||
}
|
||
if (context['FONT-FACE'].length) {
|
||
result['@FONT-FACE'] = context['FONT-FACE'];
|
||
}
|
||
if (Object.keys(context.TRANSITION).length) {
|
||
result['@TRANSITION'] = context.TRANSITION;
|
||
}
|
||
return { obj: result, messages: context.messages };
|
||
}
|
||
function trimObj(obj) {
|
||
Object.keys(obj).forEach((name) => {
|
||
const value = obj[name];
|
||
if (Object.keys(value).length === 1 && shared.hasOwn(value, '')) {
|
||
obj[name] = value[''];
|
||
}
|
||
});
|
||
}
|
||
function transform(node, context, options) {
|
||
const result = {};
|
||
node.each((child) => {
|
||
if (child.type === 'atrule') {
|
||
const body = transform(child, context, options);
|
||
if (child.name === 'font-face') {
|
||
const fontFamily = body.fontFamily;
|
||
if (fontFamily && '"\''.indexOf(fontFamily[0]) > -1) {
|
||
body.fontFamily = fontFamily.slice(1, fontFamily.length - 1);
|
||
}
|
||
context['FONT-FACE'].push(body);
|
||
}
|
||
}
|
||
else if (child.type === 'rule') {
|
||
const body = transform(child, context, options);
|
||
child.selectors.forEach((selector) => {
|
||
transformSelector(child, selector, body, result, context, options);
|
||
});
|
||
}
|
||
else if (child.type === 'decl') {
|
||
let name = child.prop;
|
||
let value = child.value;
|
||
if (options.visitor) {
|
||
const result = options.visitor(child, context);
|
||
if (!result) {
|
||
return;
|
||
}
|
||
name = result.name;
|
||
value = result.value;
|
||
}
|
||
if (child.important) {
|
||
result['!' + name] = value;
|
||
// !important的值域优先级高,故删除非!important的值域
|
||
delete result[name];
|
||
}
|
||
else {
|
||
if (!shared.hasOwn(result, '!' + name)) {
|
||
result[name] = value;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
return result;
|
||
}
|
||
function transformSelector(node, selector, body, result, context, options) {
|
||
if (options.simpleSelectorOnly) {
|
||
if (!SIMPLE_SELECTOR_RE.test(selector)) {
|
||
context.warn(node, `Invalid selector "${selector}".`);
|
||
return;
|
||
}
|
||
}
|
||
const res = selector.match(COMBINATORS_RE);
|
||
if (!res) {
|
||
return;
|
||
}
|
||
let parentSelector = res[1];
|
||
let curSelector = res[2].substring(1);
|
||
// .a.b => a.b
|
||
const dotIndex = curSelector.indexOf('.');
|
||
if (dotIndex > -1) {
|
||
parentSelector += curSelector.substring(dotIndex);
|
||
curSelector = curSelector.substring(0, dotIndex);
|
||
}
|
||
const pseudoIndex = curSelector.indexOf(':');
|
||
if (pseudoIndex > -1) {
|
||
const pseudoClass = curSelector.slice(pseudoIndex);
|
||
curSelector = curSelector.slice(0, pseudoIndex);
|
||
Object.keys(body).forEach(function (name) {
|
||
body[name + pseudoClass] = body[name];
|
||
delete body[name];
|
||
});
|
||
}
|
||
transition(curSelector, body, context);
|
||
if (!Object.keys(body).length) {
|
||
return;
|
||
}
|
||
result = (result[curSelector] || (result[curSelector] = {}));
|
||
if (result[parentSelector]) {
|
||
// clone
|
||
result[parentSelector] = processImportant(shared.extend({}, result[parentSelector], body));
|
||
}
|
||
else {
|
||
result[parentSelector] = body;
|
||
}
|
||
}
|
||
/**
|
||
* 处理 important 属性,如果某个属性是 important,需要将非 important 的该属性移除掉
|
||
* @param body
|
||
*/
|
||
function processImportant(body) {
|
||
Object.keys(body).forEach((name) => {
|
||
if (name.startsWith('!')) {
|
||
delete body[name.substring(1)];
|
||
}
|
||
});
|
||
return body;
|
||
}
|
||
function transition(className, body, { TRANSITION }) {
|
||
Object.keys(body).forEach((prop) => {
|
||
if (prop.indexOf('transition') === 0 && prop !== 'transition') {
|
||
const realProp = prop.replace('transition', '');
|
||
TRANSITION[className] = TRANSITION[className] || {};
|
||
TRANSITION[className][realProp[0].toLowerCase() + realProp.slice(1)] =
|
||
body[prop];
|
||
}
|
||
});
|
||
}
|
||
|
||
async function parse(input, options = {}) {
|
||
const { root, messages } = await postcss__default.default([
|
||
vueStyleValidator(options),
|
||
expand(options),
|
||
normalize(options),
|
||
])
|
||
.process(input, {
|
||
from: options.filename || 'foo.css',
|
||
})
|
||
.catch((err) => {
|
||
return {
|
||
root: null,
|
||
messages: [
|
||
{
|
||
type: 'error',
|
||
text: err.message,
|
||
},
|
||
],
|
||
};
|
||
});
|
||
if (options.noCode === true) {
|
||
return { code: '', messages };
|
||
}
|
||
const { obj, messages: objMessages } = root
|
||
? objectifierWithMessages(root, {
|
||
trim: !!options.trim,
|
||
parseMessages: messages,
|
||
})
|
||
: { obj: {}, messages: [] };
|
||
messages.push(...objMessages);
|
||
if (options.map || options.mapOf) {
|
||
return {
|
||
code: mapToInitStringChunk(objToMap(obj), options.ts, true, options.mapOf, options.padStyleMapOf, options.chunk),
|
||
messages,
|
||
};
|
||
}
|
||
let code = JSON.stringify(obj);
|
||
if (options.type === 'uvue') {
|
||
// TODO 暂时仅简易转换 CSS 变量
|
||
code = code.replace(/\:\s*"(.+?)"/g, function (str, p1) {
|
||
return isExpr(p1) ? `:${p1}` : str;
|
||
});
|
||
}
|
||
return { code, messages };
|
||
}
|
||
function mapToInitStringChunk(map, ts = false, isRoot = false, mapOf = '', padStyleMapOf = '', chunk = 0) {
|
||
if (!chunk) {
|
||
return mapToInitString(map, ts, isRoot, mapOf, padStyleMapOf);
|
||
}
|
||
const chunks = [];
|
||
let chunkMap = new Map();
|
||
let chunkCount = 0;
|
||
for (const [key, value] of map) {
|
||
if (chunkCount === chunk) {
|
||
chunks.push(mapToInitString(chunkMap, ts, isRoot, mapOf, padStyleMapOf));
|
||
chunkMap = new Map();
|
||
chunkCount = 0;
|
||
}
|
||
chunkMap.set(key, value);
|
||
chunkCount++;
|
||
}
|
||
if (chunkCount) {
|
||
chunks.push(mapToInitString(chunkMap, ts, isRoot, mapOf, padStyleMapOf));
|
||
}
|
||
return `[${chunks.join(',')}]`;
|
||
}
|
||
function mapToInitString(map, ts = false, isRoot = false, mapOf = '', padStyleMapOf = '') {
|
||
const entries = [];
|
||
for (let [key, value] of map) {
|
||
if (value instanceof Map) {
|
||
// trim
|
||
if (isRoot && !(value.values().next().value instanceof Map)) {
|
||
entries.push(`["${key}", ${padStyleMapOf}(${mapToInitString(value, ts, false, mapOf, padStyleMapOf)})]`);
|
||
}
|
||
else {
|
||
entries.push(`["${key}", ${mapToInitString(value, ts, false, mapOf, padStyleMapOf)}]`);
|
||
}
|
||
}
|
||
else {
|
||
entries.push(`["${key}", ${shared.isString(value) && isExpr(value) ? value : JSON.stringify(value)}]`);
|
||
}
|
||
}
|
||
if (mapOf) {
|
||
return `${mapOf}([${entries.join(', ')}])`;
|
||
}
|
||
return `new Map${ts
|
||
? isRoot
|
||
? '<string, Map<string, Map<string, any>>>'
|
||
: '<string, any>'
|
||
: ''}([${entries.join(', ')}])`;
|
||
}
|
||
function objToMap(obj) {
|
||
const map = new Map();
|
||
for (const key in obj) {
|
||
const value = obj[key];
|
||
if (typeof value === 'object') {
|
||
map.set(key, objToMap(value));
|
||
}
|
||
else {
|
||
map.set(key, value);
|
||
}
|
||
}
|
||
return map;
|
||
}
|
||
function isExpr(value) {
|
||
return value.startsWith('calc(');
|
||
}
|
||
|
||
exports.checkColor = checkColor;
|
||
exports.expand = expand;
|
||
exports.normalize = normalize;
|
||
exports.objectifier = objectifier;
|
||
exports.objectifierWithMessages = objectifierWithMessages;
|
||
exports.parse = parse;
|
||
exports.supportedPropertyReason = supportedPropertyReason;
|