From 4a9e41d3db9d48c58005e8fbcb4f774a361a0f75 Mon Sep 17 00:00:00 2001 From: EC Date: Thu, 27 Jun 2024 10:29:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=B7=B2=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=96=87=E4=BB=B6=20jsx-to-rn-style=20=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=80=BB=E8=BE=91=20(#15838)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 新增已编译文件 jsx-to-rn-style 编译逻辑 * feat: 新增 jsx-to-rn-style 支持函数对象型参数的识别 * feat: 新增 jsx-to-rn-style 支持变量型类名的识别 --------- Co-authored-by: liyixin5 --- .../src/index.ts | 265 +++++++++++------- 1 file changed, 169 insertions(+), 96 deletions(-) diff --git a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts index d20f4bd8e6a..9ed0ae424eb 100644 --- a/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts +++ b/packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts @@ -171,19 +171,149 @@ export default function (babel: { return str === '' ? [] : getMap(str) } + function getArrayExpressionFromObjectProperty (value) { + let str + + if (!value || value.value === '') { + // className: "" + return [] + } else if ((value.type === 'CallExpression' || value.type === 'Identifier') && typeof value.value !== 'string') { + // className: "".concat(classPrefix, "-left") + // className: classNames([ classPrefix, className ]) + // className: cls + return [t.callExpression(t.identifier(GET_STYLE_FUNC_NAME), [value])] + } + + return !str ? [] : getMap(str) + } + function getMatchRule (enableMultipleClassName: boolean) { if (enableMultipleClassName) { return { styleMatchRule: /[sS]tyle$/, - classNameMathRule: /[cC]lassName$/ + classNameMatchRule: /[cC]lassName$/ } } return { styleMatchRule: /^style$/, - classNameMathRule: /^className$/ + classNameMatchRule: /^className$/ + } + } + + function processStyleAndClassName ({ + attributes, + state, + existStyleImport, + isFromJSX, + }) { + const { file, opts = {} } = state + const { enableMultipleClassName = false } = opts + const { styleMatchRule, classNameMatchRule } = getMatchRule(enableMultipleClassName) + const styleNameMapping = {} + const DEFAULT_STYLE_KEY = 'style' + + attributes.forEach(attribute => { + if (isFromJSX && !t.isJSXAttribute(attribute)) return + const attrNameString = t.isJSXAttribute(attribute) ? attribute.name?.name : attribute.key?.name + if (!attrNameString || typeof attrNameString !== 'string') return + if (attrNameString.match(styleMatchRule)) { + const prefix = attrNameString.replace(styleMatchRule, '') || DEFAULT_STYLE_KEY + styleNameMapping[prefix] = { + ...styleNameMapping[prefix] || {}, + hasStyleAttribute: true, + styleAttribute: attribute + } + } + if (attrNameString.match(classNameMatchRule)) { + const prefix = attrNameString.replace(classNameMatchRule, '') || DEFAULT_STYLE_KEY + styleNameMapping[prefix] = { + ...styleNameMapping[prefix] || {}, + hasClassName: true, + classNameAttribute: attribute + } + } + }) + + for (const key in styleNameMapping) { + const { hasClassName, classNameAttribute, hasStyleAttribute, styleAttribute } = styleNameMapping[key] + + if (!(hasClassName && existStyleImport) && hasStyleAttribute && t.isStringLiteral(styleAttribute?.value)) { + const cssObject = string2Object(styleAttribute?.value?.value) + styleAttribute.value = isFromJSX + ? t.jSXExpressionContainer(object2Expression(template, cssObject)) + : object2Expression(template, cssObject) + } + + if (hasClassName && existStyleImport) { + attributes.splice(attributes.indexOf(classNameAttribute), 1) + + if (isFromJSX) { + if ( + classNameAttribute.value && + t.isJSXExpressionContainer(classNameAttribute.value) && + typeof classNameAttribute.value.expression.value !== 'string'// not like className={'container'} + ) { + file.set('injectGetStyle', true) + } + } else { + if (classNameAttribute.value) { + file.set('injectGetStyle', true) + } + } + + const arrayExpression = isFromJSX + ? getArrayExpression(classNameAttribute.value) + : getArrayExpressionFromObjectProperty(classNameAttribute.value) + + if (arrayExpression.length === 0) return + if (arrayExpression.length > 1) file.set('hasMultiStyle', true) + + if (hasStyleAttribute && styleAttribute?.value) { + file.set('hasMultiStyle', true) + let expression + // 支持 行内 style 转成oject:style="width:100;height:100;" => style={{width:'100',height:'100'}} + if (t.isStringLiteral(styleAttribute.value)) { + const cssObject = string2Object(styleAttribute.value.value) + expression = object2Expression(template, cssObject) + } else { + expression = isFromJSX ? styleAttribute.value.expression : styleAttribute.value + } + const expressionType = expression.type + + let _arrayExpression + // 非rn场景,style不支持数组,因此需要将数组转换为对象 + // style={[styles.a, styles.b]} ArrayExpression + if (expressionType === 'ArrayExpression') { + _arrayExpression = arrayExpression.concat(expression.elements) + // style={styles.a} MemberExpression + // style={{ height: 100 }} ObjectExpression + // style={{ ...custom }} ObjectExpression + // style={custom} Identifier + // style={getStyle()} CallExpression + // style={this.props.useCustom ? custom : null} ConditionalExpression + // style={custom || other} LogicalExpression + } else { + _arrayExpression = arrayExpression.concat(expression) + } + + styleAttribute.value = isFromJSX + ? t.jSXExpressionContainer(t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression)) + : t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression) + } else { + const expression = arrayExpression.length > 1 + ? t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), arrayExpression) + : arrayExpression[0] + + const newAttribute = isFromJSX + ? t.jSXAttribute(t.jSXIdentifier(key === DEFAULT_STYLE_KEY ? key : `${key}Style`), t.jSXExpressionContainer(expression)) + : t.objectProperty(t.identifier(key === DEFAULT_STYLE_KEY ? key : (key + 'Style')), expression) + attributes.push(newAttribute) + } + } } } + let existStyleImport = false return { @@ -255,106 +385,49 @@ export default function (babel: { }, JSXOpeningElement (astPath, state: PluginPass) { const { node } = astPath - const { file, opts = {} } = state - const { enableMultipleClassName = false } = opts - const { styleMatchRule, classNameMathRule } = getMatchRule(enableMultipleClassName) - - const styleNameMapping: any = {} - const DEFAULT_STYLE_KEY = 'style' const attributes = node.attributes - for (let i = 0; i < attributes.length; i++) { - const attribute = attributes[i] - if (!t.isJSXAttribute(attribute)) continue - const name = attribute.name - if (!name || typeof name.name !== 'string') continue - const attrNameString = name.name - - if (attrNameString.match(styleMatchRule)) { - const prefix = attrNameString.replace(styleMatchRule, '') || DEFAULT_STYLE_KEY - styleNameMapping[prefix] = Object.assign(styleNameMapping[prefix] || {}, { - hasStyleAttribute: true, - styleAttribute: attribute - }) - } - // 以className结尾的时候 - if (attrNameString.match(classNameMathRule)) { - const prefix = attrNameString.replace(classNameMathRule, '') || DEFAULT_STYLE_KEY - styleNameMapping[prefix] = Object.assign(styleNameMapping[prefix] || {}, { - hasClassName: true, - classNameAttribute: attribute - }) - } - } + processStyleAndClassName({ + attributes, + state, + existStyleImport, + isFromJSX: true + }) + }, + CallExpression (astPath, state: PluginPass) { + // 支持编译后代码的 style 替换 + // React.createElement 参数 - for (const key in styleNameMapping) { - const { hasClassName, classNameAttribute, hasStyleAttribute, styleAttribute } = styleNameMapping[key] - if (!(hasClassName && existStyleImport) && hasStyleAttribute) { - if (t.isStringLiteral(styleAttribute.value)) { - const cssObject = string2Object(styleAttribute.value.value) - styleAttribute.value = t.jSXExpressionContainer(object2Expression(template, cssObject)) - } + const { node } = astPath + // 检查是否是 React.createElement 调用 + if ( + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, { name: 'React' }) && + t.isIdentifier(node.callee.property, { name: 'createElement' }) + ) { + const args = node.arguments + if (args.length <= 1) return + + let attributes + + if (t.isCallExpression(args[1]) && t.isObjectExpression(args[1].arguments[0])) { + // _object_spread({ className: rootClass(), style: rootStyle() }, rest) + attributes = args[1].arguments[0].properties } - - if (hasClassName && existStyleImport) { - // Remove origin className - attributes.splice(attributes.indexOf(classNameAttribute), 1) - - if ( - classNameAttribute.value && - classNameAttribute.value.type === 'JSXExpressionContainer' && - typeof classNameAttribute.value.expression.value !== 'string'// not like className={'container'} - ) { - file.set('injectGetStyle', true) - } - - const arrayExpression = getArrayExpression(classNameAttribute.value) - - if (arrayExpression.length === 0) { - return - } - - if (arrayExpression.length > 1) { - file.set('hasMultiStyle', true) - } - - if (hasStyleAttribute && styleAttribute.value) { - file.set('hasMultiStyle', true) - let expression - // 支持 行内 style 转成oject:style="width:100;height:100;" => style={{width:'100',height:'100'}} - if (t.isStringLiteral(styleAttribute.value)) { - const cssObject = string2Object(styleAttribute.value.value) - expression = object2Expression(template, cssObject) - } else { - expression = styleAttribute.value.expression - } - const expressionType = expression.type - - let _arrayExpression - // 非rn场景,style不支持数组,因此需要将数组转换为对象 - // style={[styles.a, styles.b]} ArrayExpression - if (expressionType === 'ArrayExpression') { - _arrayExpression = arrayExpression.concat(expression.elements) - // style={styles.a} MemberExpression - // style={{ height: 100 }} ObjectExpression - // style={{ ...custom }} ObjectExpression - // style={custom} Identifier - // style={getStyle()} CallExpression - // style={this.props.useCustom ? custom : null} ConditionalExpression - // style={custom || other} LogicalExpression - } else { - _arrayExpression = arrayExpression.concat(expression) - } - styleAttribute.value = t.jSXExpressionContainer(t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), _arrayExpression)) - } else { - const expression = arrayExpression.length > 1 - ? t.callExpression(t.identifier(MERGE_ELE_STYLES_FUNC_NAME), arrayExpression) - : arrayExpression[0] - attributes.push(t.jSXAttribute(t.jSXIdentifier(key === DEFAULT_STYLE_KEY ? key : (key + 'Style')), t.jSXExpressionContainer(expression))) - } + if (t.isObjectExpression(args[1])) { + // { className: ..., style: ... } + attributes = args[1].properties } + if (!attributes) return + + processStyleAndClassName({ + attributes, + state, + existStyleImport, + isFromJSX: false + }) } - } + }, } } }