Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 新增已编译文件 jsx-to-rn-style 编译逻辑 #15838

Merged
merged 5 commits into from
Jun 27, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 169 additions & 96 deletions packages/babel-plugin-transform-react-jsx-to-rn-stylesheet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,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 {
Expand Down Expand Up @@ -254,106 +384,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
})
}
}
},
}
}
}
Expand Down