Skip to content

Commit

Permalink
feat(taro-rn): rn 样式转换支持多className 转换 (#9742)
Browse files Browse the repository at this point in the history
* feat(taro-rn): rn 样式转换支持多className 转换

* feat(transform-react-jsx-to-rn-stylesheet): 增加 enableMultipleClassName 配置, 支持多类名转换

* feat(babel-preset-taro): babel preset 中增加 enableMultipleClassName 配置

Co-authored-by: croatialu <[email protected]>
Co-authored-by: tony chen <[email protected]>
  • Loading branch information
3 people authored Jul 21, 2021
1 parent 1c66a93 commit e367ca8
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,82 @@ class App extends Component {
}]} />);
}
}

```

support multiple className to style


.babelrc
``` json

{
"plugins": ["transform-react-jsx-to-rn-stylesheet", { enableMultipleClassName: true }]
}

```



``` js

import { createElement, Component } from 'rax';
import './app.css';

class App extends Component {
render() {
return <div className="container" headerClassName="header" />;
}
}

/* ↓ ↓ ↓ ↓ ↓ ↓ */

import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;

class App extends Component {
render() {
return <div style={_styleSheet["container"]} headerStyle={_styleSheet["header"]} />;
}

}

```

the `enableMultipleClassName` option will match 'attribute' end with 'className' | 'style', and transform className to style.

but use the error css value in style attribute

like this:

``` js

import { createElement, Component } from 'rax';
import './app.css';

class App extends Component {
render() {
return <StatusBar barStyle="dark-content" />;
}
}

```

the plugin can't transform 'dark-content' to css value, so this transformation will be ignored


``` js
import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;

class App extends Component {
render() {
return <StatusBar barStyle={"dark-content"} />;
}

}


```
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ const getStyleFunctionTemplete = `function _getStyle(classNameExpression) {

describe('jsx style plugin', () => {
function getTransfromCode (source, debug = false, options = {}) {
const { isCSSModule, enableMultipleClassName = false } = options
const code = transform(source, {
plugins: [[jSXStylePlugin, { isCSSModule: options.isCSSModule }], syntaxJSX],
plugins: [[jSXStylePlugin, { isCSSModule, enableMultipleClassName }], syntaxJSX],
configFile: false
}).code
if (debug) {
Expand Down Expand Up @@ -524,6 +525,94 @@ class App extends Component {
render() {
return <div style={[_styleSheet["header"], styleSheet.red]} />;
}\n
}`)
})

it('disableMultipleClassName and transform multiple className to multiple style', () => {
expect(getTransfromCode(`
import { createElement, Component } from 'rax';
import './app.css';
class App extends Component {
render() {
return <div className="container" headerClassName="header" />;
}
}`, false, { enableMultipleClassName: false })).toBe(`import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;
class App extends Component {
render() {
return <div headerClassName="header" style={_styleSheet["container"]} />;
}
}`)
})

it('enableMultipleClassName and transform multiple className to multiple style', () => {
expect(getTransfromCode(`
import { createElement, Component } from 'rax';
import './app.css';
class App extends Component {
render() {
return <div className="container" headerClassName="header" />;
}
}`, false, { enableMultipleClassName: true })).toBe(`import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;
class App extends Component {
render() {
return <div style={_styleSheet["container"]} headerStyle={_styleSheet["header"]} />;
}
}`)
})

it('enableMultipleClassName and transform multiple className to multiple style as array', () => {
expect(getTransfromCode(`
import { createElement, Component } from 'rax';
import './app.css';
class App extends Component {
render() {
return <div className="container" headerClassName="header" style={{ color: "red" }} headerStyle={{ color: "green" }} />;
}
}`, false, { enableMultipleClassName: true })).toBe(`import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;
class App extends Component {
render() {
return <div style={[_styleSheet["container"], {
color: "red"
}]} headerStyle={[_styleSheet["header"], {
color: "green"
}]} />;
}
}`)
})

it('enableMultipleClassName and transform error css value', () => {
expect(getTransfromCode(`
import { createElement, Component } from 'rax';
import './app.css';
class App extends Component {
render() {
return <StatusBar barStyle="dark-content" />;
}
}`, false, { enableMultipleClassName: true })).toBe(`import { createElement, Component } from 'rax';
import appCssStyleSheet from "./app.css";
var _styleSheet = appCssStyleSheet;
class App extends Component {
render() {
return <StatusBar barStyle={"dark-content"} />;
}
}`)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,13 @@ const string2Object = str => {
arr[1] = arr[1]?.replace(/px/g, 'PX')
return arr
})
const cssObject = transformCSS(entries)
return cssObject
// css 转换报错时,使用原来的值
try {
const cssObject = transformCSS(entries)
return cssObject
} catch {
return str
}
}

const object2Expression = (template, cssObject) => {
Expand Down Expand Up @@ -166,6 +171,19 @@ export default function (babel: {
return str === '' ? [] : getMap(str)
}

function getMatchRule (enableMultipleClassName: boolean) {
if (enableMultipleClassName) {
return {
styleMatchRule: /[sS]tyle$/,
classNameMathRule: /[cC]lassName$/
}
}

return {
styleMatchRule: /^style$/,
classNameMathRule: /^className$/
}
}
let existStyleImport = false

return {
Expand Down Expand Up @@ -220,85 +238,92 @@ export default function (babel: {
}
},
JSXOpeningElement ({ node }, state: PluginPass) {
const { file } = state
const { file, opts } = state
const { enableMultipleClassName } = opts
const { styleMatchRule, classNameMathRule } = getMatchRule(enableMultipleClassName)
const cssModuleStylesheets = file.get('cssModuleStylesheets') || []

// Check if has "style"
let hasStyleAttribute = false
let styleAttribute
let hasClassName = false
let classNameAttribute

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)) {
const name = attribute.name
if (name) {
if (!hasStyleAttribute) {
hasStyleAttribute = name.name === 'style'
styleAttribute = hasStyleAttribute && attributes[i]
}

if (!hasClassName) {
hasClassName = name.name === 'className'
classNameAttribute = hasClassName && 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
})
}
}

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'}
!isCSSModuleExpression(classNameAttribute.value, cssModuleStylesheets) // 不含有 css module 变量的表达式
) {
file.set('injectGetStyle', true)
// 以className结尾的时候
if (attrNameString.match(classNameMathRule)) {
const prefix = attrNameString.replace(classNameMathRule, '') || DEFAULT_STYLE_KEY
styleNameMapping[prefix] = Object.assign(styleNameMapping[prefix] || {}, {
hasClassName: true,
classNameAttribute: attribute
})
}
}
for (const key in styleNameMapping) {
const { hasClassName, classNameAttribute, hasStyleAttribute, styleAttribute } = styleNameMapping[key]
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'}
!isCSSModuleExpression(classNameAttribute.value, cssModuleStylesheets) // 不含有 css module 变量的表达式
) {
file.set('injectGetStyle', true)
}

const arrayExpression = getArrayExpression(classNameAttribute.value, cssModuleStylesheets)
const arrayExpression = getArrayExpression(classNameAttribute.value, cssModuleStylesheets)

if (arrayExpression.length === 0) {
return
}
if (arrayExpression.length === 0) {
return
}

if (hasStyleAttribute && styleAttribute.value) {
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)
if (hasStyleAttribute && styleAttribute.value) {
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

// style={[styles.a, styles.b]} ArrayExpression
if (expressionType === 'ArrayExpression') {
expression.elements = 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 {
styleAttribute.value = t.jSXExpressionContainer(t.arrayExpression(arrayExpression.concat(expression)))
}
} else {
expression = styleAttribute.value.expression
const expression = arrayExpression.length === 1 ? arrayExpression[0] : t.arrayExpression(arrayExpression)
attributes.push(t.jSXAttribute(t.jSXIdentifier(key === DEFAULT_STYLE_KEY ? key : (key + 'Style')), t.jSXExpressionContainer(expression)))
}
const expressionType = expression.type

// style={[styles.a, styles.b]} ArrayExpression
if (expressionType === 'ArrayExpression') {
expression.elements = 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 {
styleAttribute.value = t.jSXExpressionContainer(t.arrayExpression(arrayExpression.concat(expression)))
} else if (hasStyleAttribute) {
if (t.isStringLiteral(styleAttribute.value)) {
const cssObject = string2Object(styleAttribute.value.value)
styleAttribute.value = t.jSXExpressionContainer(object2Expression(template, cssObject))
}
} else {
const expression = arrayExpression.length === 1 ? arrayExpression[0] : t.arrayExpression(arrayExpression)
attributes.push(t.jSXAttribute(t.jSXIdentifier('style'), t.jSXExpressionContainer(expression)))
}
} else if (hasStyleAttribute) {
if (t.isStringLiteral(styleAttribute.value)) {
const cssObject = string2Object(styleAttribute.value.value)
styleAttribute.value = t.jSXExpressionContainer(object2Expression(template, cssObject))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { PluginPass } from 'babel__core'

export interface PluginOptions {
isCSSModule?: boolean

/* 是否开启多类名转换 */
enableMultipleClassName?: boolean
}

export interface ConvertPluginPass extends PluginPass {
Expand Down
Loading

0 comments on commit e367ca8

Please sign in to comment.