diff --git a/docs/src/content/docs/v3/other/babel-plugin.mdx b/docs/src/content/docs/v3/other/babel-plugin.mdx
index ab400087..4d4e135e 100644
--- a/docs/src/content/docs/v3/other/babel-plugin.mdx
+++ b/docs/src/content/docs/v3/other/babel-plugin.mdx
@@ -64,6 +64,21 @@ const stylesheet = StyleSheet.create((theme, rt) => ({
}))
```
+
+
### 2. Attaching unique id to each StyleSheet
diff --git a/plugin/__tests__/dependencies.spec.js b/plugin/__tests__/dependencies.spec.js
index f30fe4aa..178438bc 100644
--- a/plugin/__tests__/dependencies.spec.js
+++ b/plugin/__tests__/dependencies.spec.js
@@ -506,7 +506,7 @@ pluginTester({
container: (headerColors, colorMap) => ({
backgroundColor: headerColors[rt.colorScheme],
paddingBottom: colorMap[theme.colors.primary],
- uni__dependencies: [5, 0]
+ uni__dependencies: [0, 5]
})
}),
664955283
@@ -559,7 +559,7 @@ pluginTester({
translateY: -rt.insets.ime
}
],
- uni__dependencies: [9]
+ uni__dependencies: [14]
}
}),
664955283
@@ -627,7 +627,7 @@ pluginTester({
if (someRandomInt === 5) {
return {
backgroundColor: theme.colors.background,
- uni__dependencies: [0]
+ uni__dependencies: [0, 9, 11]
}
}
@@ -635,19 +635,19 @@ pluginTester({
return {
backgroundColor: theme.colors.barbie,
paddingBottom: rt.insets.bottom,
- uni__dependencies: [0, 9]
+ uni__dependencies: [0, 9, 11]
}
}
if (someRandomInt === 15) {
return {
fontSize: rt.fontScale * 10,
- uni__dependencies: [11]
+ uni__dependencies: [0, 9, 11]
}
} else {
return {
backgroundColor: theme.colors.blood,
- uni__dependencies: [0]
+ uni__dependencies: [0, 9, 11]
}
}
}
@@ -857,6 +857,105 @@ pluginTester({
664955283
)
`
+ },
+ {
+ title: 'Should correctly detect dependencies in weirdest syntax',
+ code: `
+ import { View, Text } from 'react-native'
+ import { StyleSheet } from 'react-native-unistyles'
+
+ export const Example = () => {
+ return (
+
+ Hello world
+
+ )
+ }
+
+ const styles = StyleSheet.create(({ components: { test, other: { nested }} }, { insets: { ime }, screen, statusBar: { width, height } }) => {
+ const otherVariable = 2
+
+ return {
+ container: () => {
+ if (otherVariable === 2) {
+ return {
+ backgroundColor: nested
+ }
+ }
+
+ if (otherVariable === 3) {
+ return {
+ marginTop: ime,
+ height: screen.height
+ }
+ }
+
+ return nested
+ },
+ container2: () => ({
+ paddingBottom: ime,
+ height,
+ width
+ })
+ }
+ })
+ `,
+ output: `
+ import { Text } from 'react-native-unistyles/components/native/Text'
+ import { View } from 'react-native-unistyles/components/native/View'
+
+ import { StyleSheet } from 'react-native-unistyles'
+
+ export const Example = () => {
+ return (
+
+ Hello world
+
+ )
+ }
+
+ const styles = StyleSheet.create(
+ (
+ {
+ components: {
+ test,
+ other: { nested }
+ }
+ },
+ { insets: { ime }, screen, statusBar: { width, height } }
+ ) => {
+ const otherVariable = 2
+
+ return {
+ container: () => {
+ if (otherVariable === 2) {
+ return {
+ backgroundColor: nested,
+ uni__dependencies: [0, 14, 6]
+ }
+ }
+
+ if (otherVariable === 3) {
+ return {
+ marginTop: ime,
+ height: screen.height,
+ uni__dependencies: [0, 14, 6]
+ }
+ }
+
+ return { ...nested, uni__dependencies: [0, 14, 6] }
+ },
+ container2: () => ({
+ paddingBottom: ime,
+ height,
+ width,
+ uni__dependencies: [14, 12]
+ })
+ }
+ },
+ 664955283
+ )
+ `
}
]
})
diff --git a/plugin/__tests__/ref.spec.js b/plugin/__tests__/ref.spec.js
deleted file mode 100644
index 0939f306..00000000
--- a/plugin/__tests__/ref.spec.js
+++ /dev/null
@@ -1,471 +0,0 @@
-import { pluginTester } from 'babel-plugin-tester'
-import plugin from '../'
-
-pluginTester({
- plugin,
- pluginOptions: {
- debug: false
- },
- babelOptions: {
- plugins: ['@babel/plugin-syntax-jsx'],
- generatorOpts: {
- retainLines: true
- }
- },
- tests: [
- {
- title: 'Does nothing if there is no import from React Native',
- code: `
- import { StyleSheet, View, Text } from 'custom-lib'
-
- export const Example = () => {
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `,
- output: `
- import { StyleSheet, View, Text } from 'custom-lib'
-
- export const Example = () => {
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `
- },
- {
- title: 'Preserves user\'s ref as function',
- code: `
- import { useRef } from 'react'
- import { View, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = useRef()
-
- return (
- {
- doSomething(ref)
- myRef.current = ref
- }}
- style={styles.container}
- >
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { View } from 'react-native-unistyles/components/native/View'
- import { useRef } from 'react'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = useRef()
-
- return (
- {
- doSomething(ref)
- myRef.current = ref
- }}
- style={styles.container}
- >
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create(
- {
- container: {
- backgroundColor: 'red'
- }
- },
- 92366683
- )
- `
- },
- {
- title: 'Preserves user\'s ref as function with cleanup',
- code: `
- import React from 'react'
- import { View, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
-
- return (
- {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup()
- }
- }}
- style={styles.container}
- >
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { View } from 'react-native-unistyles/components/native/View'
- import React from 'react'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
-
- return (
- {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup()
- }
- }}
- style={styles.container}
- >
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create(
- {
- container: {
- backgroundColor: 'red'
- }
- },
- 92366683
- )
- `
- },
- {
- title: 'Preserves user\'s ref as assigned arrow function',
- code: `
- import React from 'react'
- import { View, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
- const fn = ref => {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup2()
- }
- }
-
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { View } from 'react-native-unistyles/components/native/View'
- import React from 'react'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
- const fn = ref => {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup2()
- }
- }
-
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create(
- {
- container: {
- backgroundColor: 'red'
- }
- },
- 92366683
- )
- `
- },
- {
- title: 'Preserves user\'s ref as assigned function function',
- code: `
- import React from 'react'
- import { View, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
- function fn(ref) {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup2()
- }
- }
-
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create({
- container: {
- backgroundColor: 'red'
- }
- })
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { View } from 'react-native-unistyles/components/native/View'
- import React from 'react'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- const myRef = React.useRef()
- function fn(ref) {
- doSomething(ref)
- myRef.current = ref
-
- return () => {
- customCleanup2()
- }
- }
-
- return (
-
- Hello world
-
- )
- }
-
- const styles = StyleSheet.create(
- {
- container: {
- backgroundColor: 'red'
- }
- },
- 92366683
- )
- `
- },
- {
- title: 'Should keep order of spreads',
- code: `
- import { View } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- return (
-
- )
- }
-
- const styles = StyleSheet.create(theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor
- },
- secondProp: {
- marginHorizontal: theme.gap(10),
- backgroundColor: 'red'
- },
- thirdProp: {
- backgroundColor: 'blue'
- }
- }))
- `,
- output: `
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- return
- }
-
- const styles = StyleSheet.create(
- theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor,
- uni__dependencies: [0]
- },
- secondProp: {
- marginHorizontal: theme.gap(10),
- backgroundColor: 'red',
- uni__dependencies: [0]
- },
- thirdProp: {
- backgroundColor: 'blue'
- }
- }),
- 92366683
- )
- `
- },
- {
- title: 'Should support nested styles',
- code: `
- import { View } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ styles }) => {
- return (
-
- )
- }
-
- const styles = StyleSheet.create(theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor
- }
- }))
- `,
- output: `
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ styles }) => {
- return
- }
-
- const styles = StyleSheet.create(
- theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor,
- uni__dependencies: [0]
- }
- }),
- 92366683
- )
- `
- },
- {
- title: 'Should support conditional styles',
- code: `
- import { View } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ condition }) => {
- return (
-
- )
- }
-
- const styles = StyleSheet.create(theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor
- }
- }))
- `,
- output: `
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ condition }) => {
- return
- }
-
- const styles = StyleSheet.create(
- theme => ({
- container: {
- flex: 1,
- alignItems: 'center',
- justifyContent: 'center',
- backgroundColor: theme.colors.backgroundColor,
- uni__dependencies: [0]
- }
- }),
- 92366683
- )
- `
- }
- ]
-})
diff --git a/plugin/__tests__/stylesheet.spec.js b/plugin/__tests__/stylesheet.spec.js
index a741ed06..767d1e19 100644
--- a/plugin/__tests__/stylesheet.spec.js
+++ b/plugin/__tests__/stylesheet.spec.js
@@ -283,7 +283,7 @@ pluginTester({
backgroundColor: 'red',
variants: {},
paddingTop: rt.insets.top,
- uni__dependencies: [4, 9]
+ uni__dependencies: [9, 4]
})
}),
798826616
@@ -332,7 +332,7 @@ pluginTester({
backgroundColor: hhsa.colors.background,
variants: {},
paddingTop: dee.colorScheme === 'dark' ? 0 : 10,
- uni__dependencies: [0, 4, 5]
+ uni__dependencies: [0, 5, 4]
})
}),
798826616
@@ -383,14 +383,14 @@ pluginTester({
backgroundColor: theme.colors.background,
variants: {},
paddingTop: rt.insets.top,
- uni__dependencies: [0, 4, 9]
+ uni__dependencies: [0, 9, 4]
})
}
}, 798826616)
`
},
{
- title: 'Should generates two different ids for 2 stylesheets in the same file',
+ title: 'Should generate two different ids for 2 stylesheets in the same file',
code: `
import { View, Text } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
@@ -442,7 +442,7 @@ pluginTester({
backgroundColor: theme.colors.background,
variants: {},
paddingTop: rt.insets.top,
- uni__dependencies: [0, 4, 9]
+ uni__dependencies: [0, 9, 4]
})
}
}, 798826616)
@@ -452,202 +452,12 @@ pluginTester({
backgroundColor: theme.colors.background,
variants: {},
paddingTop: rt.insets.top,
- uni__dependencies: [0, 4, 9]
+ uni__dependencies: [0, 9, 4]
})
}
}, 798826617)
`
},
- {
- title: 'Should do nothing if pressable is parameterless arrow function and style is an object',
- code: `
- import { View, Pressable, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- return (
-
- styles.pressable}>
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create((theme, rt) => {
- return {
- container: () => ({
- backgroundColor: theme.colors.background,
- variants: {},
- paddingTop: rt.insets.top
- }),
- pressable: {
- marginRight: arg1 + arg2
- }
- }
- })
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { Pressable } from 'react-native-unistyles/components/native/Pressable'
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = () => {
- return (
-
- styles.pressable}>
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create((theme, rt) => {
- return {
- container: () => ({
- backgroundColor: theme.colors.background,
- variants: {},
- paddingTop: rt.insets.top,
- uni__dependencies: [0, 4, 9]
- }),
- pressable: {
- marginRight: arg1 + arg2
- }
- }
- }, 798826616)
- `
- },
- {
- title: 'Should handle pressable with arrow function and array of styles',
- code: `
- import { View, Pressable, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ height }) => {
- return (
-
- [styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed]}>
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create((theme, rt) => ({
- sectionItem: {
- width: 100,
- height: 100,
- theme: theme.colors.red
- },
- pressed: {
- marginBottom: rt.insets.bottom
- }
- }))
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { Pressable } from 'react-native-unistyles/components/native/Pressable'
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ height }) => {
- return (
-
- [styles.sectionItem, styles.other(1), { height }, pressed && styles.pressed]}>
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create(
- (theme, rt) => ({
- sectionItem: {
- width: 100,
- height: 100,
- theme: theme.colors.red,
- uni__dependencies: [0]
- },
- pressed: {
- marginBottom: rt.insets.bottom,
- uni__dependencies: [9]
- }
- }),
- 798826616
- )
- `
- },
- {
- title: 'Should handle nested functions',
- code: `
- import { View, Pressable, Text } from 'react-native'
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ height }) => {
- return (
-
- [styles.sectionItem, { height }, pressed && styles.pressed(pressed), pressed ? styles.pressed : styles.notPressed]}>
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create((theme, rt) => ({
- sectionItem: {
- width: 100,
- height: 100,
- theme: theme.colors.red
- },
- pressed: pressed => ({
- marginBottom: rt.insets.bottom
- })
- }))
- `,
- output: `
- import { Text } from 'react-native-unistyles/components/native/Text'
- import { Pressable } from 'react-native-unistyles/components/native/Pressable'
- import { View } from 'react-native-unistyles/components/native/View'
-
- import { StyleSheet } from 'react-native-unistyles'
-
- export const Example = ({ height }) => {
- return (
-
- [
- styles.sectionItem,
- { height },
- pressed && styles.pressed(pressed),
- pressed ? styles.pressed : styles.notPressed
- ]}
- >
- Hello world
-
-
- )
- }
-
- const styles = StyleSheet.create(
- (theme, rt) => ({
- sectionItem: {
- width: 100,
- height: 100,
- theme: theme.colors.red,
- uni__dependencies: [0]
- },
- pressed: pressed => ({
- marginBottom: rt.insets.bottom,
- uni__dependencies: [9]
- })
- }),
- 798826616
- )
- `
- },
{
title: 'Should use same local name as user name while replacing imports',
code: `
diff --git a/plugin/common.js b/plugin/common.js
deleted file mode 100644
index 5936da15..00000000
--- a/plugin/common.js
+++ /dev/null
@@ -1,154 +0,0 @@
-function getIdentifierNameFromExpression(t, memberExpression) {
- if (t.isIdentifier(memberExpression)) {
- return [memberExpression.name]
- }
-
- if (t.isSpreadElement(memberExpression)) {
- return [getIdentifierNameFromExpression(t, memberExpression.argument)].flat()
- }
-
- if (t.isObjectProperty(memberExpression)) {
- return [getIdentifierNameFromExpression(t, memberExpression.value)].flat()
- }
-
- if (t.isMemberExpression(memberExpression)) {
- if (memberExpression.computed) {
- return [
- getIdentifierNameFromExpression(t, memberExpression.property),
- getIdentifierNameFromExpression(t, memberExpression.object)
- ].flat()
- }
-
- const object = memberExpression.object
-
- // If the object is an Identifier, return its name
- if (t.isIdentifier(object)) {
- return [object.name]
- }
-
- // If the object is another MemberExpression, recursively get the identifier
- if (t.isMemberExpression(object)) {
- return getIdentifierNameFromExpression(t, object).flat()
- }
- }
-
- if (t.isBinaryExpression(memberExpression)) {
- return [
- getIdentifierNameFromExpression(t, memberExpression.left),
- getIdentifierNameFromExpression(t, memberExpression.right)
- ].flat()
- }
-
- if (t.isCallExpression(memberExpression)) {
- return getIdentifierNameFromExpression(t, memberExpression.callee)
- }
-
- if (t.isConditionalExpression(memberExpression)) {
- return [
- getIdentifierNameFromExpression(t, memberExpression.test.left),
- getIdentifierNameFromExpression(t, memberExpression.test.right),
- getIdentifierNameFromExpression(t, memberExpression.alternate),
- getIdentifierNameFromExpression(t, memberExpression.consequent),
- getIdentifierNameFromExpression(t, memberExpression.test)
- ].flat()
- }
-
- if (t.isArrayExpression(memberExpression)) {
- return memberExpression.elements.map(expression => getIdentifierNameFromExpression(t, expression)).flat()
- }
-
- if (t.isArrowFunctionExpression(memberExpression)) {
- return memberExpression.body.properties.map(prop => getIdentifierNameFromExpression(t, prop.value)).flat()
- }
-
- if (t.isTemplateLiteral(memberExpression)) {
- return memberExpression.expressions.map(expression => getIdentifierNameFromExpression(t, expression)).flat()
- }
-
- if (t.isObjectExpression(memberExpression)) {
- return memberExpression.properties
- .filter(property => t.isObjectProperty(property))
- .flatMap(property => getIdentifierNameFromExpression(t, property.value))
- }
-
- if (t.isUnaryExpression(memberExpression)) {
- return getIdentifierNameFromExpression(t, memberExpression.argument.object)
- }
-
- return []
-}
-
-function getSecondPropertyName(t, memberExpression) {
- if (t.isUnaryExpression(memberExpression)) {
- return getSecondPropertyName(t, memberExpression.argument.object)
- }
-
- if (t.isConditionalExpression(memberExpression)) {
- return [
- getSecondPropertyName(t, memberExpression.test.left),
- getSecondPropertyName(t, memberExpression.test.right),
- getSecondPropertyName(t, memberExpression.alternate),
- getSecondPropertyName(t, memberExpression.consequent),
- getSecondPropertyName(t, memberExpression.test)
- ].flat()
- }
-
- if (t.isTemplateLiteral(memberExpression)) {
- return memberExpression.expressions.map(expression => getSecondPropertyName(t, expression)).flat()
- }
-
- if (t.isBinaryExpression(memberExpression)) {
- return [
- getSecondPropertyName(t, memberExpression.left),
- getSecondPropertyName(t, memberExpression.right)
- ].flat()
- }
-
- if (t.isObjectExpression(memberExpression)) {
- return memberExpression.properties
- .filter(property => t.isObjectProperty(property))
- .flatMap(property => getSecondPropertyName(t, property.value))
- }
-
- if (t.isArrayExpression(memberExpression)) {
- return memberExpression.elements.map(expression => getSecondPropertyName(t, expression)).flat()
- }
-
- if (!t.isMemberExpression(memberExpression)) {
- return []
- }
-
- let current = memberExpression.computed
- ? memberExpression.property
- : memberExpression
- let propertyName = null
-
- while (t.isMemberExpression(current)) {
- propertyName = current.property
- current = current.object
- }
-
- // special case for IME
- if (propertyName && t.isIdentifier(propertyName) && propertyName.name === 'insets') {
- if (t.isIdentifier(memberExpression.property) && memberExpression.property.name === "ime") {
- return [memberExpression.property.name]
- }
-
- return [propertyName.name]
- }
-
- if (propertyName && t.isIdentifier(propertyName)) {
- return [propertyName.name]
- }
-
- if (propertyName) {
- return [propertyName.value]
- }
-
- return []
-}
-
-module.exports = {
- getIdentifierNameFromExpression,
- getSecondPropertyName
-}
diff --git a/plugin/index.js b/plugin/index.js
index 9fc34d1e..3d805138 100644
--- a/plugin/index.js
+++ b/plugin/index.js
@@ -1,6 +1,6 @@
const { addUnistylesImport, isInsideNodeModules } = require('./import')
const { hasStringRef } = require('./ref')
-const { isUnistylesStyleSheet, analyzeDependencies, addStyleSheetTag, getUnistyles, isKindOfStyleSheet, maybeAddThemeDependencyToMemberExpression, addThemeDependencyToMemberExpression, getStyleSheetLocalNames } = require('./stylesheet')
+const { isUnistylesStyleSheet, addStyleSheetTag, isKindOfStyleSheet, getStylesDependenciesFromFunction, addDependencies, getStylesDependenciesFromObject } = require('./stylesheet')
const { extractVariants } = require('./variants')
const { REACT_NATIVE_COMPONENT_NAMES, REPLACE_WITH_UNISTYLES_PATHS, REPLACE_WITH_UNISTYLES_EXOTIC_PATHS, NATIVE_COMPONENTS_PATHS } = require('./consts')
const { handleExoticImport } = require('./exotic')
@@ -149,43 +149,38 @@ module.exports = function ({ types: t }) {
const arg = path.node.arguments[0]
- // Object passed to StyleSheet.create
+ // Object passed to StyleSheet.create (may contain variants)
if (t.isObjectExpression(arg)) {
- arg.properties.forEach(property => {
- if (t.isObjectProperty(property)) {
- const propertyValues = getUnistyles(t, property)
+ const detectedDependencies = getStylesDependenciesFromObject(t, path)
- propertyValues.forEach(propertyValue => {
- analyzeDependencies(t, state, property.key.name, propertyValue, [], [])
+ if (detectedDependencies) {
+ if (t.isObjectExpression(arg)) {
+ arg.properties.forEach(property => {
+ if (detectedDependencies[property.key.name]) {
+ addDependencies(t, state, property.key.name, property, detectedDependencies[property.key.name])
+ }
})
}
- })
+ }
}
// Function passed to StyleSheet.create (e.g., theme => ({ container: {} }))
if (t.isArrowFunctionExpression(arg) || t.isFunctionExpression(arg)) {
- const localNames = getStyleSheetLocalNames(t, arg)
- const body = t.isBlockStatement(arg.body)
- ? arg.body.body.find(statement => t.isReturnStatement(statement)).argument
- : arg.body
-
- // Ensure the function body returns an object
- if (t.isObjectExpression(body)) {
- body.properties.forEach(property => {
- if (t.isObjectProperty(property)) {
- const propertyValues = getUnistyles(t, property)
-
- // special case for non object/function properties
- // maybe user used inlined theme? ({ container: theme.components.container })
- if (propertyValues.length === 0 && maybeAddThemeDependencyToMemberExpression(t, property, localNames.theme)) {
- addThemeDependencyToMemberExpression(t, property)
+ const detectedDependencies = getStylesDependenciesFromFunction(t, path)
+
+ if (detectedDependencies) {
+ const body = t.isBlockStatement(arg.body)
+ ? arg.body.body.find(statement => t.isReturnStatement(statement)).argument
+ : arg.body
+
+ // Ensure the function body returns an object
+ if (t.isObjectExpression(body)) {
+ body.properties.forEach(property => {
+ if (detectedDependencies[property.key.name]) {
+ addDependencies(t, state, property.key.name, property, detectedDependencies[property.key.name])
}
-
- propertyValues.forEach(propertyValue => {
- analyzeDependencies(t, state, property.key.name, propertyValue, localNames.theme, localNames.miniRuntime)
- })
- }
- })
+ })
+ }
}
}
}
diff --git a/plugin/stylesheet.js b/plugin/stylesheet.js
index 4c92fb45..7a0f630a 100644
--- a/plugin/stylesheet.js
+++ b/plugin/stylesheet.js
@@ -1,5 +1,3 @@
-const { getIdentifierNameFromExpression, getSecondPropertyName } = require('./common')
-
const UnistyleDependency = {
Theme: 0,
ThemeName: 1,
@@ -19,7 +17,7 @@ const UnistyleDependency = {
}
function stringToUniqueId(str) {
- let hash = 0;
+ let hash = 0
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) - hash + str.charCodeAt(i)
@@ -63,268 +61,505 @@ function addStyleSheetTag(t, path, state) {
callee.container.arguments.push(t.numericLiteral(uniqueId))
}
-function getStyleSheetLocalNames(t, functionArg) {
- const params = functionArg.params
- const hasTheme = params.length >= 1
- const hasMiniRuntime = params.length === 2
- const getProperty = (property, allowNested) => {
- if (t.isIdentifier(property.value)) {
- return property.value.name
+const getProperty = (t, property) => {
+ if (!property) {
+ return undefined
+ }
+
+ if (t.isIdentifier(property)) {
+ return {
+ properties: [property.name]
}
+ }
+
+ if (t.isObjectPattern(property)) {
+ const matchingProperties = property.properties.flatMap(p => getProperty(t, p))
- if (!t.isObjectPattern(property.value)) {
- return undefined
+ return {
+ properties: matchingProperties.flatMap(properties => properties.properties)
}
+ }
- if (allowNested) {
- return property.value.properties.flatMap(getProperty)
+ if (t.isObjectProperty(property) && t.isIdentifier(property.value)) {
+ return {
+ properties: [property.key.name]
}
+ }
- // we can force allow nested only for insets
- const hasIme = property.value.properties.find(property => property.key.name === 'ime')
- const lastKeyValue = property.value.properties.flatMap(getProperty)
+ if (t.isObjectProperty(property) && t.isObjectPattern(property.value)) {
+ const matchingProperties = property.value.properties.flatMap(p => getProperty(t, p))
- if (hasIme) {
- return lastKeyValue
+ return {
+ parent: property.key.name,
+ properties: matchingProperties.flatMap(properties => properties.properties)
}
-
- return `${property.key.name}.${lastKeyValue}`
}
- const getLocalNames = (param, allowNested) => {
- if (t.isObjectPattern(param)) {
- return param.properties
- .flatMap(property => getProperty(property, allowNested))
- .filter(Boolean)
+
+ return undefined
+}
+
+function getStylesDependenciesFromObject(t, path) {
+ const detectedStylesWithVariants = new Set()
+ const stylesheet = path.node.arguments[0]
+
+ stylesheet.properties.forEach(property => {
+ if (!t.isIdentifier(property.key)) {
+ return
}
+ if (t.isObjectProperty(property)) {
+ if(t.isObjectExpression(property.value)) {
+ property.value.properties.forEach(innerProp => {
+ if (t.isIdentifier(innerProp.key) && innerProp.key.name === 'variants') {
+ detectedStylesWithVariants.add({
+ label: 'variants',
+ key: property.key.name
+ })
+ }
+ })
- if (t.isIdentifier(param)) {
- return [param.name]
+ }
}
- return []
+ if (t.isArrowFunctionExpression(property.value)) {
+ if(t.isObjectExpression(property.value.body)) {
+ property.value.body.properties.forEach(innerProp => {
+ if (t.isIdentifier(innerProp.key) && innerProp.key.name === 'variants') {
+ detectedStylesWithVariants.add({
+ label: 'variants',
+ key: property.key.name
+ })
+ }
+ })
+
+ }
+ }
+ })
+
+ const variants = Array.from(detectedStylesWithVariants)
+
+ return variants.reduce((acc, { key, label }) => {
+ if (acc[key]) {
+ return {
+ ...acc,
+ [key]: [
+ ...acc[key],
+ label
+ ]
+ }
+ }
+
+ return {
+ ...acc,
+ [key]: [label]
+ }
+ }, [])
+}
+
+function getStylesDependenciesFromFunction(t, path) {
+ const funcPath = path.get('arguments.0')
+
+ if (!funcPath) {
+ return
}
- return {
- theme: hasTheme ? getLocalNames(params[0], true) : [],
- miniRuntime: hasMiniRuntime ? getLocalNames(params[1], false) : []
+ const params = funcPath.node.params
+ const [themeParam, rtParam] = params
+
+ let themeNames = []
+
+ // destructured theme object
+ if (themeParam.type === 'ObjectPattern') {
+ // If destructured, collect all property names
+ for (const prop of themeParam.properties) {
+ themeNames.push(getProperty(t, prop))
+ }
}
-}
-function maybeAddThemeDependencyToMemberExpression(t, property, themeLocalNames) {
- if (t.isIdentifier(property)) {
- return themeLocalNames.includes(property.name)
+ // user used 'theme' without destructuring
+ if (themeParam.type === 'Identifier') {
+ themeNames.push({
+ properties: [themeParam.name]
+ })
}
- if (t.isObjectProperty(property)) {
- return maybeAddThemeDependencyToMemberExpression(t, property.value, themeLocalNames)
+ let rtNames = []
+
+ // destructured rt object
+ if (rtParam && rtParam.type === 'ObjectPattern') {
+ // If destructured, collect all property names
+ for (const prop of rtParam.properties) {
+ rtNames.push(getProperty(t, prop))
+ }
}
- if (t.isMemberExpression(property)) {
- return maybeAddThemeDependencyToMemberExpression(t, property.object, themeLocalNames)
+ // user used 'rt' without destructuring
+ if (rtParam && rtParam.type === 'Identifier') {
+ rtNames.push({
+ properties: [rtParam.name]
+ })
}
-}
+ // get returned object or return statement from StyleSheet.create function
+ let returnedObjectPath = null
-/** @param {import('./index').UnistylesPluginPass} state */
-function analyzeDependencies(t, state, name, unistyleObj, themeNames, rtNames) {
- const debugMessage = deps => {
- if (state.opts.debug) {
- const mappedDeps = deps
- .map(dep => Object.keys(UnistyleDependency).find(key => UnistyleDependency[key] === dep))
- .join(', ')
+ if (funcPath.get('body').isObjectExpression()) {
+ returnedObjectPath = funcPath.get('body')
+ } else {
+ funcPath.traverse({
+ ReturnStatement(retPath) {
+ if (!returnedObjectPath && retPath.get('argument').isObjectExpression()) {
+ returnedObjectPath = retPath.get('argument')
+ }
+ }
+ })
+ }
- console.log(`${state.filename.replace(`${state.file.opts.root}/`, '')}: styles.${name}: [${mappedDeps}]`)
- }
+ if (!returnedObjectPath) {
+ // there is no returned object
+ // abort
+
+ return
}
- const unistyle = unistyleObj.properties
- const dependencies = []
- Object.values(unistyle).forEach(uni => {
- const identifiers = getIdentifierNameFromExpression(t, uni)
+ const detectedStylesWithVariants = new Set()
+
+ // detect variants via Scope
+ returnedObjectPath.get('properties').forEach(propPath => {
+ // get style name
+ const stylePath = propPath.get('key')
- if (themeNames.some(name => identifiers.some(id => id === name))) {
- dependencies.push(UnistyleDependency.Theme)
+ if (!stylePath.isIdentifier()) {
+ return
}
- const matchingRtNames = rtNames.reduce((acc, name) => {
- if (name.includes('.')) {
- const key = name.split('.').at(0)
+ const styleKey = stylePath.node.name
- if (identifiers.some(id => name.includes(id))) {
- return [
- ...acc,
- key
- ]
- }
+ const valuePath = propPath.get('value')
+
+ if (valuePath.isObjectExpression()) {
+ const hasVariants = valuePath.get('properties').some(innerProp => {
+ const innerKey = innerProp.get('key')
+
+ return innerKey.isIdentifier() && innerKey.node.name === 'variants'
+ })
- return acc
+ if (hasVariants) {
+ detectedStylesWithVariants.add({
+ label: 'variants',
+ key: styleKey
+ })
}
+ }
+ if (valuePath.isArrowFunctionExpression()) {
+ if(t.isObjectExpression(valuePath.node.body)) {
+ const hasVariants = valuePath.node.body.properties.some(innerProp => {
- if (identifiers.some(id => id === name)) {
- return [
- ...acc,
- name
- ]
+ return t.isIdentifier(innerProp.key) && innerProp.key.name === 'variants'
+ })
+
+ if (hasVariants) {
+ detectedStylesWithVariants.add({
+ label: 'variants',
+ key: styleKey
+ })
+ }
}
+ }
+ })
- return acc
- }, [])
+ const detectedStylesWithTheme = new Set()
- if (matchingRtNames.length > 0) {
- const propertyNames = getSecondPropertyName(t, uni.value)
+ // detect theme dependencies via Scope
+ themeNames.forEach(({ properties }) => {
+ properties.forEach(property => {
+ const binding = funcPath.scope.getBinding(property)
- matchingRtNames
- .concat(propertyNames)
- .filter(Boolean)
- .forEach(propertyName => {
- switch (propertyName) {
- case 'themeName': {
- dependencies.push(UnistyleDependency.ThemeName)
+ if (!binding) {
+ return
+ }
- return
- }
- case 'adaptiveThemes': {
- dependencies.push(UnistyleDependency.AdaptiveThemes)
+ binding.referencePaths.forEach(refPath => {
+ // find key of the style that we are referring to
+ const containerProp = refPath
+ .findParent(parent => parent.isObjectProperty() && parent.parentPath === returnedObjectPath)
- return
- }
- case 'breakpoint': {
- dependencies.push(UnistyleDependency.Breakpoints)
+ if (!containerProp) {
+ return
+ }
- return
- }
- case 'colorScheme': {
- dependencies.push(UnistyleDependency.ColorScheme)
+ const keyNode = containerProp.get('key')
+ const styleKey = keyNode.isIdentifier()
+ ? keyNode.node.name
+ : keyNode.isLiteral()
+ ? keyNode.node.value
+ : null
+
+ if (styleKey) {
+ detectedStylesWithTheme.add({
+ label: 'theme',
+ key: styleKey
+ })
+ }
+ })
+ })
+ })
- return
- }
- case 'screen': {
- dependencies.push(UnistyleDependency.Dimensions)
+ const detectedStylesWithRt = new Set()
+ const localRtName = t.isIdentifier(rtParam)
+ ? rtParam.name
+ : undefined
- return
- }
- case 'isPortrait':
- case 'isLandscape': {
- dependencies.push(UnistyleDependency.Orientation)
+ // detect rt dependencies via Scope
+ rtNames.forEach(({ properties, parent }) => {
+ properties.forEach(property => {
+ const rtBinding = funcPath.scope.getBinding(property)
- return
- }
- case 'contentSizeCategory': {
- dependencies.push(UnistyleDependency.ContentSizeCategory)
+ if (!rtBinding) {
+ return
+ }
- return
- }
- case 'ime': {
- dependencies.push(UnistyleDependency.Ime)
+ const isValidDependency = Boolean(toUnistylesDependency(property))
- return
- }
- case 'insets': {
- dependencies.push(UnistyleDependency.Insets)
+ let validRtName = property
- return
- }
- case 'pixelRatio': {
- dependencies.push(UnistyleDependency.PixelRatio)
+ // user used nested destructing, find out parent key
+ if (!isValidDependency && (!localRtName || (localRtName && localRtName !== property))) {
+ if (!parent) {
+ return
+ }
- return
- }
- case 'fontScale': {
- dependencies.push(UnistyleDependency.FontScale)
+ if (!Boolean(toUnistylesDependency(parent))) {
+ return
+ }
- return
- }
- case 'statusBar': {
- dependencies.push(UnistyleDependency.StatusBar)
+ validRtName = parent
+ }
+
+ rtBinding.referencePaths.forEach(refPath => {
+ // to detect rt dependencies we need to get parameter not rt itself
+ // eg. rt.screen.width -> screen
+ // rt.insets.top -> insets
+ // special case: rt.insets.ime -> ime
+
+ let usedLabel = validRtName
- return
+ if (refPath.parentPath.isMemberExpression() && refPath.parentPath.get('object') === refPath) {
+ const memberExpr = refPath.parentPath
+ const propPath = memberExpr.get('property')
+
+ if (propPath.isIdentifier()) {
+ if (localRtName) {
+ usedLabel = propPath.node.name
}
- case 'navigationBar': {
- dependencies.push(UnistyleDependency.NavigationBar)
- return
+ if (
+ usedLabel === 'insets' &&
+ memberExpr.parentPath.isMemberExpression() &&
+ memberExpr.parentPath.get('object') === memberExpr
+ ) {
+ const secondPropPath = memberExpr.parentPath.get('property')
+
+ if (secondPropPath.isIdentifier() && secondPropPath.node.name === 'ime') {
+ usedLabel = 'ime'
+ }
}
}
- })
- }
+ }
+
+ // find key of the style that we are referring to
+ const containerProp = refPath
+ .findParent(parent => parent.isObjectProperty() && parent.parentPath === returnedObjectPath)
+
+ if (!containerProp) {
+ return
+ }
- if (uni.key && uni.key.name === 'variants') {
- dependencies.push(UnistyleDependency.Variants)
+ const keyNode = containerProp.get('key')
+ const styleKey = keyNode.isIdentifier()
+ ? keyNode.node.name
+ : keyNode.isLiteral()
+ ? keyNode.node.value
+ : null
+
+ if (styleKey) {
+ detectedStylesWithRt.add({
+ label: usedLabel,
+ key: styleKey
+ })
+ }
+ })
+ })
+ })
+
+ const variants = Array.from(detectedStylesWithVariants)
+ const theme = Array.from(detectedStylesWithTheme)
+ const rt = Array.from(detectedStylesWithRt)
+
+ return theme
+ .concat(rt)
+ .concat(variants)
+ .reduce((acc, { key, label }) => {
+ if (acc[key]) {
+ return {
+ ...acc,
+ [key]: [
+ ...acc[key],
+ label
+ ]
+ }
+ }
+
+ return {
+ ...acc,
+ [key]: [label]
+ }
+ }, [])
+}
+
+function toUnistylesDependency(dependency) {
+ switch (dependency) {
+ case 'theme': {
+ return UnistyleDependency.Theme
+ }
+ case 'themeName': {
+ return UnistyleDependency.ThemeName
+ }
+ case 'adaptiveThemes': {
+ return UnistyleDependency.AdaptiveThemes
+ }
+ case 'breakpoint': {
+ return UnistyleDependency.Breakpoints
+ }
+ case 'colorScheme': {
+ return UnistyleDependency.ColorScheme
+ }
+ case 'screen': {
+ return UnistyleDependency.Dimensions
+ }
+ case 'isPortrait':
+ case 'isLandscape': {
+ return UnistyleDependency.Orientation
+ }
+ case 'contentSizeCategory': {
+ return UnistyleDependency.ContentSizeCategory
+ }
+ case 'ime': {
+ return UnistyleDependency.Ime
+ }
+ case 'insets': {
+ return UnistyleDependency.Insets
+ }
+ case 'pixelRatio': {
+ return UnistyleDependency.PixelRatio
+ }
+ case 'fontScale': {
+ return UnistyleDependency.FontScale
+ }
+ case 'statusBar': {
+ return UnistyleDependency.StatusBar
+ }
+ case 'navigationBar': {
+ return UnistyleDependency.NavigationBar
+ }
+ case 'variants': {
+ return UnistyleDependency.Variants
}
// breakpoints are too complex and are handled by C++
- })
+ }
+}
- // add dependencies to the unistyle object if any found
- if (dependencies.length > 0) {
- const uniqueDependencies = Array.from(new Set(dependencies))
+function getReturnStatementsFromBody(t, node, results = []) {
+ if (t.isReturnStatement(node)) {
+ results.push(node)
+ }
- debugMessage(uniqueDependencies)
+ if (t.isBlockStatement(node)) {
+ node.body.forEach(child => getReturnStatementsFromBody(t, child, results))
+ }
+
+ if (t.isIfStatement(node)) {
+ getReturnStatementsFromBody(t, node.consequent, results)
- unistyleObj.properties.push(
- t.objectProperty(
- t.identifier('uni__dependencies'),
- t.arrayExpression(uniqueDependencies.map(dep => t.numericLiteral(dep)))
- )
- )
+ if (node.alternate) {
+ getReturnStatementsFromBody(t, node.alternate, results)
+ }
}
+
+ return results
}
-function getUnistyles(t, property) {
- const propertyValue = t.isArrowFunctionExpression(property.value)
- ? property.value.body
- : property.value
+function addDependencies(t, state, styleName, unistyle, detectedDependencies) {
+ const debugMessage = deps => {
+ if (state.opts.debug) {
+ const mappedDeps = deps
+ .map(dep => Object.keys(UnistyleDependency).find(key => UnistyleDependency[key] === dep))
+ .join(', ')
- if (t.isObjectExpression(propertyValue)) {
- return [propertyValue]
+ console.log(`${state.filename.replace(`${state.file.opts.root}/`, '')}: styles.${styleName}: [${mappedDeps}]`)
+ }
}
- if (t.isBlockStatement(propertyValue)) {
- // here we might have single return statement
- // or if-else statements with return statements
- return propertyValue.body
- .flatMap(value => {
- if (t.isReturnStatement(value)) {
- return [value]
- }
+ const styleDependencies = detectedDependencies.map(toUnistylesDependency)
- if (!t.isIfStatement(value)) {
- return []
- }
+ // add metadata about dependencies
+ if (styleDependencies.length > 0) {
+ const uniqueDependencies = Array.from(new Set(styleDependencies))
+
+ debugMessage(uniqueDependencies)
+
+ let targets = []
- return [value.consequent, value.alternate]
- .filter(Boolean)
- .flatMap(value => {
- if (t.isBlockStatement(value)) {
- return value.body.filter(t.isReturnStatement)
+ if (t.isArrowFunctionExpression(unistyle.value) || t.isFunctionExpression(unistyle.value)) {
+ if (t.isObjectExpression(unistyle.value.body)) {
+ targets.push(unistyle.value.body)
+ }
+
+ if (t.isBlockStatement(unistyle.value.body)) {
+ targets = getReturnStatementsFromBody(t, unistyle.value.body)
+ .map(node => {
+ if (t.isIdentifier(node.argument)) {
+ node.argument = t.objectExpression([
+ t.spreadElement(node.argument)
+ ])
}
+
+ return node.argument
})
- })
- .map(value => value.argument)
- }
+ }
+ }
- return []
-}
+ if (t.isObjectExpression(unistyle.value)) {
+ targets.push(unistyle.value)
+ }
+
+ if (t.isMemberExpression(unistyle.value)) {
+ // convert to object
+ unistyle.value = t.objectExpression([t.spreadElement(unistyle.value)])
-function addThemeDependencyToMemberExpression(t, path) {
- path.value = t.objectExpression([
- t.spreadElement(path.value),
- t.objectProperty(
- t.identifier('uni__dependencies'),
- t.arrayExpression([t.numericLiteral(UnistyleDependency.Theme)])
- )
- ])
+ targets.push(unistyle.value)
+ }
+
+ if (targets.length > 0) {
+ targets.forEach(target => {
+ target.properties.push(
+ t.objectProperty(
+ t.identifier('uni__dependencies'),
+ t.arrayExpression(uniqueDependencies.map(dep => t.numericLiteral(dep)))
+ )
+ )
+ })
+ }
+ }
}
module.exports = {
isUnistylesStyleSheet,
- analyzeDependencies,
+ addDependencies,
addStyleSheetTag,
- getUnistyles,
- isKindOfStyleSheet,
- getStyleSheetLocalNames,
- maybeAddThemeDependencyToMemberExpression,
- addThemeDependencyToMemberExpression
+ getStylesDependenciesFromObject,
+ getStylesDependenciesFromFunction,
+ isKindOfStyleSheet
}