1608 lines
48 KiB
JavaScript
1608 lines
48 KiB
JavaScript
|
'use strict';
|
|||
|
|
|||
|
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_\-\:]+)+)$/;
|
|||
|
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$1 = (fn) => {
|
|||
|
const cache = Object.create(null);
|
|||
|
return ((str) => {
|
|||
|
const hit = cache[str];
|
|||
|
return hit || (cache[str] = fn(str));
|
|||
|
});
|
|||
|
};
|
|||
|
const hyphenateRE$1 = /([A-Z])/g;
|
|||
|
const hyphenateStyleProperty = cacheStringFunction$1((str) => str
|
|||
|
.replace(hyphenateRE$1, (_, 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 getSupportedPlatforms(uniPlatform) {
|
|||
|
const supportedPlatforms = [];
|
|||
|
if (uniPlatform?.app?.android?.unixVer !== 'x') {
|
|||
|
supportedPlatforms.push('app-android');
|
|||
|
}
|
|||
|
if (uniPlatform?.app.ios?.unixVer !== 'x') {
|
|||
|
supportedPlatforms.push('app-ios');
|
|||
|
}
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
const backgroundColor = 'background-color' ;
|
|||
|
const backgroundImage = 'background-image' ;
|
|||
|
const transformBackground = (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)];
|
|||
|
}
|
|||
|
return [decl];
|
|||
|
};
|
|||
|
|
|||
|
const borderWidth = '-width' ;
|
|||
|
const borderStyle = '-style' ;
|
|||
|
const borderColor = '-color' ;
|
|||
|
const transformBorder = (decl) => {
|
|||
|
const { prop, value, important, raws, source } = decl;
|
|||
|
const splitResult = value.replace(/\s*,\s*/g, ',').split(/\s+/);
|
|||
|
const result = [/^[\d\.]+\S*$/, /^(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),
|
|||
|
];
|
|||
|
};
|
|||
|
|
|||
|
/**
|
|||
|
* Make a map and return a function for checking if a key
|
|||
|
* is in that map.
|
|||
|
* IMPORTANT: all calls of this function must be prefixed with
|
|||
|
* \/\*#\_\_PURE\_\_\*\/
|
|||
|
* So that rollup can tree-shake them if necessary.
|
|||
|
*/
|
|||
|
|
|||
|
(process.env.NODE_ENV !== 'production')
|
|||
|
? Object.freeze({})
|
|||
|
: {};
|
|||
|
(process.env.NODE_ENV !== 'production') ? Object.freeze([]) : [];
|
|||
|
const extend = Object.assign;
|
|||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
|||
|
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
|
|||
|
const isFunction = (val) => typeof val === 'function';
|
|||
|
const isString = (val) => typeof val === 'string';
|
|||
|
const cacheStringFunction = (fn) => {
|
|||
|
const cache = Object.create(null);
|
|||
|
return ((str) => {
|
|||
|
const hit = cache[str];
|
|||
|
return hit || (cache[str] = fn(str));
|
|||
|
});
|
|||
|
};
|
|||
|
const camelizeRE = /-(\w)/g;
|
|||
|
/**
|
|||
|
* @private
|
|||
|
*/
|
|||
|
const camelize = cacheStringFunction((str) => {
|
|||
|
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
|
|||
|
});
|
|||
|
const hyphenateRE = /\B([A-Z])/g;
|
|||
|
/**
|
|||
|
* @private
|
|||
|
*/
|
|||
|
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
|
|||
|
|
|||
|
const borderTop = 'border-top-' ;
|
|||
|
const borderRight = 'border-right-' ;
|
|||
|
const borderBottom = 'border-bottom-' ;
|
|||
|
const borderLeft = 'border-left-' ;
|
|||
|
const transformBorderColor = (decl) => {
|
|||
|
const { prop, value, important, raws, source } = decl;
|
|||
|
let property = 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 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) => {
|
|||
|
const { value, important, raws, source } = decl;
|
|||
|
const splitResult = value.split(/\s+/);
|
|||
|
if (value.includes('/')) {
|
|||
|
return [decl];
|
|||
|
}
|
|||
|
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 transformBorderStyle = transformBorderColor;
|
|||
|
|
|||
|
const transformBorderWidth = transformBorderColor;
|
|||
|
|
|||
|
const flexDirection = 'flex-direction' ;
|
|||
|
const flexWrap = 'flex-wrap' ;
|
|||
|
const transformFlexFlow = (decl) => {
|
|||
|
const { value, important, raws, source } = decl;
|
|||
|
const splitResult = value.split(/\s+/);
|
|||
|
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) => {
|
|||
|
const { value, important, raws, source } = decl;
|
|||
|
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 = value.split(/\s+/);
|
|||
|
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) => {
|
|||
|
const CHUNK_REGEXP = /^(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?\s*(\S*)?\s*(\d*\.?\d+(?:ms|s)?)?$/;
|
|||
|
const { value, important, raws, source } = decl;
|
|||
|
const result = [];
|
|||
|
const 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;
|
|||
|
};
|
|||
|
|
|||
|
function getDeclTransforms(options) {
|
|||
|
const styleMap = {
|
|||
|
transition: transformTransition,
|
|||
|
border: transformBorder,
|
|||
|
background: transformBackground,
|
|||
|
borderTop: transformBorder,
|
|||
|
borderRight: transformBorder,
|
|||
|
borderBottom: transformBorder,
|
|||
|
borderLeft: transformBorder,
|
|||
|
borderStyle: transformBorderStyle,
|
|||
|
borderWidth: transformBorderWidth,
|
|||
|
borderColor: transformBorderColor,
|
|||
|
borderRadius: transformBorderRadius,
|
|||
|
// uvue已经支持这些简写属性,不需要展开
|
|||
|
/* eslint-disable no-restricted-syntax */
|
|||
|
...(options.type !== 'uvue'
|
|||
|
? {
|
|||
|
margin: transformMargin,
|
|||
|
padding: transformPadding,
|
|||
|
flexFlow: transformFlexFlow,
|
|||
|
}
|
|||
|
: {}),
|
|||
|
};
|
|||
|
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: 'nvue:expand',
|
|||
|
Declaration(decl) {
|
|||
|
if (decl[expanded]) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (!DeclTransforms) {
|
|||
|
DeclTransforms = getDeclTransforms(options);
|
|||
|
}
|
|||
|
const transform = DeclTransforms[decl.prop];
|
|||
|
if (transform) {
|
|||
|
const res = transform(decl);
|
|||
|
const isSame = res.length === 1 && res[0] === decl;
|
|||
|
if (!isSame) {
|
|||
|
decl.replaceWith(res);
|
|||
|
}
|
|||
|
}
|
|||
|
decl[expanded] = true;
|
|||
|
},
|
|||
|
};
|
|||
|
return plugin;
|
|||
|
}
|
|||
|
|
|||
|
const normalizeColor = (v) => {
|
|||
|
v = (v || '').toString();
|
|||
|
if (v.match(/^#[0-9a-fA-F]{6}$/)) {
|
|||
|
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);
|
|||
|
},
|
|||
|
};
|
|||
|
};
|
|||
|
// 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',
|
|||
|
};
|
|||
|
|
|||
|
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();
|
|||
|
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 (isFunction(res)) {
|
|||
|
return res(k, v, result);
|
|||
|
}
|
|||
|
})
|
|||
|
.join('\n');
|
|||
|
},
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
const normalizeTransform = (v) => {
|
|||
|
return { value: v };
|
|||
|
};
|
|||
|
|
|||
|
const normalizeInterval = (v) => {
|
|||
|
v = (v || 0).toString();
|
|||
|
let match, num;
|
|||
|
if ((match = v.match(/^\d*\.?\d+(ms|s)?$/))) {
|
|||
|
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) => {
|
|||
|
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,
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
// 从 property.ts 中移动到 map 里,避免循环依赖
|
|||
|
const normalizeProperty = (v, options) => {
|
|||
|
v = (v || '').toString();
|
|||
|
v = v
|
|||
|
.split(/\s*,\s*/)
|
|||
|
.map(camelize)
|
|||
|
.join(',');
|
|||
|
if (v.split(/\s*,\s*/).every((p) => !!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,
|
|||
|
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() {
|
|||
|
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 = camelize(property.name);
|
|||
|
let normalize;
|
|||
|
if (uvueNormalizeMap[prop]) {
|
|||
|
normalize = uvueNormalizeMap[prop];
|
|||
|
}
|
|||
|
else {
|
|||
|
const normalizes = getNormalizes(property);
|
|||
|
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;
|
|||
|
}
|
|||
|
function getNormalizes(property) {
|
|||
|
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: !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();
|
|||
|
}
|
|||
|
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 (!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) => {
|
|||
|
selector = selector
|
|||
|
.replace(/\s*([\+\~\>])\s*/g, '$1')
|
|||
|
.replace(/\s+/, ' ');
|
|||
|
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;
|
|||
|
}
|
|||
|
decl.prop = camelize(decl.prop);
|
|||
|
const { value, log } = normalizeDecl(decl, options);
|
|||
|
if (isString(value) || isNumber(value)) {
|
|||
|
decl.value = value;
|
|||
|
}
|
|||
|
if (log && log.reason && helper) {
|
|||
|
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 (isFunction(normalize)) {
|
|||
|
if (!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 (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: 'WARNING: `' +
|
|||
|
hyphenateStyleProperty(name) +
|
|||
|
'` is not a standard property name (may not be supported)',
|
|||
|
};
|
|||
|
}
|
|||
|
return {
|
|||
|
value: result.value,
|
|||
|
log,
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
function objectifier(node) {
|
|||
|
if (!node) {
|
|||
|
return {};
|
|||
|
}
|
|||
|
const context = {
|
|||
|
'FONT-FACE': [],
|
|||
|
TRANSITION: {},
|
|||
|
};
|
|||
|
const result = transform(node, context);
|
|||
|
if (context['FONT-FACE'].length) {
|
|||
|
result['@FONT-FACE'] = context['FONT-FACE'];
|
|||
|
}
|
|||
|
if (Object.keys(context.TRANSITION).length) {
|
|||
|
result['@TRANSITION'] = context.TRANSITION;
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
function transform(node, context) {
|
|||
|
const result = {};
|
|||
|
node.each((child) => {
|
|||
|
if (child.type === 'atrule') {
|
|||
|
const body = transform(child, context);
|
|||
|
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);
|
|||
|
child.selectors.forEach((selector) => {
|
|||
|
transformSelector(selector, body, result, context);
|
|||
|
});
|
|||
|
}
|
|||
|
else if (child.type === 'decl') {
|
|||
|
if (child.important) {
|
|||
|
result['!' + child.prop] = child.value;
|
|||
|
// !important的值域优先级高,故删除非!important的值域
|
|||
|
delete result[child.prop];
|
|||
|
}
|
|||
|
else {
|
|||
|
if (!hasOwn(result, '!' + child.prop)) {
|
|||
|
result[child.prop] = child.value;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
return result;
|
|||
|
}
|
|||
|
function transformSelector(selector, body, result, context) {
|
|||
|
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(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([
|
|||
|
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 = root ? objectifier(root) : {};
|
|||
|
if (options.map || options.mapOf) {
|
|||
|
return {
|
|||
|
code: mapToInitStringChunk(objToMap(obj), options.ts, true, options.mapOf, options.chunk),
|
|||
|
messages,
|
|||
|
};
|
|||
|
}
|
|||
|
return { code: JSON.stringify(obj), messages };
|
|||
|
}
|
|||
|
function mapToInitStringChunk(map, ts = false, isRoot = false, isMapOf = false, chunk = 0) {
|
|||
|
if (!chunk) {
|
|||
|
return mapToInitString(map, ts, isRoot, isMapOf);
|
|||
|
}
|
|||
|
const chunks = [];
|
|||
|
let chunkMap = new Map();
|
|||
|
let chunkCount = 0;
|
|||
|
for (const [key, value] of map) {
|
|||
|
if (chunkCount === chunk) {
|
|||
|
chunks.push(mapToInitString(chunkMap, ts, isRoot, isMapOf));
|
|||
|
chunkMap = new Map();
|
|||
|
chunkCount = 0;
|
|||
|
}
|
|||
|
chunkMap.set(key, value);
|
|||
|
chunkCount++;
|
|||
|
}
|
|||
|
if (chunkCount) {
|
|||
|
chunks.push(mapToInitString(chunkMap, ts, isRoot, isMapOf));
|
|||
|
}
|
|||
|
return `[${chunks.join(',')}]`;
|
|||
|
}
|
|||
|
function mapToInitString(map, ts = false, isRoot = false, isMapOf = false) {
|
|||
|
const entries = [];
|
|||
|
for (let [key, value] of map) {
|
|||
|
if (value instanceof Map) {
|
|||
|
entries.push(`["${key}", ${mapToInitString(value, ts, false, isMapOf)}]`);
|
|||
|
}
|
|||
|
else {
|
|||
|
entries.push(`["${key}", ${JSON.stringify(value)}]`);
|
|||
|
}
|
|||
|
}
|
|||
|
if (isMapOf) {
|
|||
|
return `utsMapOf([${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;
|
|||
|
}
|
|||
|
|
|||
|
exports.expand = expand;
|
|||
|
exports.normalize = normalize;
|
|||
|
exports.objectifier = objectifier;
|
|||
|
exports.parse = parse;
|