Skip to content

Commit b6c7b39

Browse files
committed
Introduce runtime props
1 parent 429f476 commit b6c7b39

File tree

13 files changed

+273
-127
lines changed

13 files changed

+273
-127
lines changed

plugin/keys.js

Lines changed: 0 additions & 19 deletions
This file was deleted.

plugin/plugin.js

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ const {
44
isMemberExpression,
55
} = require('@babel/types')
66
const postcss = require('postcss')
7-
const { getStylesForProperty } = require('css-to-react-native')
8-
const {colorKeys, stringKeys} = require('./keys')
7+
const { transform } = require('./transform')
98

109
const STYLED = 'styled'
1110
const CSS = 'css'
11+
const MIXIN = 'MIXIN_'
1212
const MAGIC_NUMBER = 123456789
13-
const REGEX = new RegExp(`([^0-9]*)(\\d+\\.${MAGIC_NUMBER})([^0-9]*)`)
13+
const REGEX = new RegExp(`\\d+\.\\d+|[^0-9]+`, 'g')
1414
function kebabToCamel(str) {
1515
return str.replace(/-./g, match => match.charAt(1).toUpperCase());
1616
}
@@ -71,6 +71,23 @@ module.exports = function plugin(babel) {
7171
}
7272
}
7373

74+
/**
75+
* Input:
76+
* quasis = ['prop1: 1px;\nprop2: ', 'px;\nprop3: ', 'px;\n']
77+
* expressions = [2, (props) => props.number]
78+
*
79+
* Output:
80+
* cssText = '
81+
* prop1: 1px;
82+
* prop2: 0.123456789px;
83+
* prop3: 1.123456789px;
84+
* '
85+
*
86+
* substitutionMap = {
87+
* 0.123456789: 2,
88+
* 1.123456789: (props) => props.number,
89+
* }
90+
*/
7491
function extractSubstitutionMap({
7592
quasis,
7693
expressions,
@@ -97,35 +114,25 @@ function extractSubstitutionMap({
97114
}
98115

99116
function parseCss(cssText, substitutionMap) {
117+
cssText = cssText.replace(/\/\/.*(?=\n|$)/g, '') // remove inline comments
100118
const lines = cssText.split('\n')
101-
const isComment = (key) => key.startsWith('//')
119+
let styles = []
102120
for (let i = 0; i < lines.length; i++) {
103121
let line = lines[i].trim()
104-
if (isComment(line)) {
105-
lines[i] = ''
106-
}
107122
if (line.endsWith(';')) {
108123
line = line.substring(0, line.length - 1)
109124
}
110125
if (substitutionMap[line]) { // mixin
111-
line = `${line}:${line};`
112-
lines[i] = line
126+
styles.push([MIXIN, line])
127+
lines[i] = ''
113128
}
114129
}
115130
cssText = lines.join('')
116-
let styles = []
117131
const { nodes } = postcss.parse(cssText)
118132
for (const node of nodes) {
119133
if (node.type === 'decl') {
120134
const key = kebabToCamel(node.prop)
121-
let value = node.value
122-
let styleObject
123-
if (colorKeys.includes(key) || stringKeys.includes(key)) {
124-
styleObject = { [key]: value }
125-
} else {
126-
styleObject = getStylesForProperty(key, value)
127-
}
128-
styles = styles.concat(Object.entries(styleObject))
135+
styles = styles.concat(transform(key, node.value))
129136
}
130137
}
131138

@@ -134,21 +141,21 @@ function parseCss(cssText, substitutionMap) {
134141
}
135142

136143
function buildCssObject(identifier, t, substitutions) {
137-
function wrapper(args) {
144+
function maybeDynamic(fn, args) {
138145
return t.callExpression(
139146
t.memberExpression(
140147
t.identifier(identifier),
141148
t.identifier('maybeDynamic')
142149
),
143-
args
150+
[fn].concat(args)
144151
)
145152
}
146153
function caller(args) {
147154
return t.functionExpression(null, [], t.blockStatement([t.returnStatement(args)]))
148155
}
149156

150157
function splitSubstitution(str) {
151-
return str.match(REGEX)?.slice(1) ?? []
158+
return str.match(REGEX) ?? []
152159
}
153160

154161
function isSubstitution(value) {
@@ -166,6 +173,9 @@ function buildCssObject(identifier, t, substitutions) {
166173
const elements = []
167174
const expressions = []
168175
const matches = splitSubstitution(value)
176+
if (substitutions[matches[0]]) {
177+
elements.push(t.templateElement({ raw: '' }))
178+
}
169179
for (const match of matches) {
170180
const substitution = substitutions[match]
171181
if (substitution) {
@@ -174,6 +184,9 @@ function buildCssObject(identifier, t, substitutions) {
174184
elements.push(t.templateElement({ raw: match }))
175185
}
176186
}
187+
if (substitutions[matches[matches.length - 1]]) {
188+
elements.push(t.templateElement({ raw: '' }))
189+
}
177190
return t.templateLiteral(elements, expressions)
178191
}
179192
return t.stringLiteral(value)
@@ -212,7 +225,7 @@ function buildCssObject(identifier, t, substitutions) {
212225
const mapper = travers(args)
213226
let expression = mapper(value)
214227
if (args.length) {
215-
expression = wrapper([caller(expression)].concat(args))
228+
expression = maybeDynamic(caller(expression), args)
216229
}
217230
values.push(t.arrayExpression([t.stringLiteral(key), expression]))
218231
}

plugin/transform.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const { getStylesForProperty } = require('css-to-react-native')
2+
3+
const colorKeys = ['background', 'borderColor', 'color', 'backgroundColor', 'tintColor', 'shadowColor']
4+
const stringKeys = [
5+
'flexDirection',
6+
'justifyContent',
7+
'alignItems',
8+
'alignSelf',
9+
'alignContent',
10+
'fontStyle',
11+
'fontFamily',
12+
'fontWeight',
13+
'textAlign',
14+
'resizeMode',
15+
'tintColor',
16+
'overflow',
17+
'position',
18+
'direction',
19+
'display',
20+
]
21+
22+
const runtimeKeys = ['border']
23+
24+
const withoutTransform = (key, value) => ({ [key]: value })
25+
const runtimeTransform = (key, value) => ({ 'RUNTIME_': [key, value] })
26+
27+
const colorTransforms = colorKeys.map((key) => [key, withoutTransform])
28+
const stringTransforms = stringKeys.map((key) => [key, withoutTransform])
29+
const runtimeTransforms = runtimeKeys.map((key) => [key, runtimeTransform])
30+
31+
const CustomTransformers = Object.fromEntries(
32+
[]
33+
.concat(colorTransforms)
34+
.concat(stringTransforms)
35+
.concat(runtimeTransforms)
36+
)
37+
38+
function transform(key, value) {
39+
const transformer = CustomTransformers[key] ?? getStylesForProperty
40+
41+
return Object.entries(transformer(key, value))
42+
}
43+
44+
module.exports = {
45+
transform,
46+
}

src/__tests__/buildDynamicStyles.test.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
1-
import { buildDynamicStyles, maybeDynamic, mixin } from '../styled'
1+
import { maybeDynamic } from '../maybeDynamic'
2+
import { buildDynamicStyles, createStyled } from '../styled'
23

34

45
describe('buildDynamicStyles', () => {
56
test('Should return primitives if no function is passed in the arguments', () => {
7+
const { css } = createStyled()
68
const props = Object.freeze({
79
isInitalProps: true,
810
theme: {},
911
})
10-
const mixin1 = mixin([
12+
const mixin1 = css([
1113
['mixin1number', 1],
1214
['mixin1string', 'mixin1string'],
15+
// @ts-expect-error
1316
['mixin1boolean', true],
1417
['mixin1array', [{ scale: 2 }]],
1518
['mixin1object', { scale: 2 }],
19+
// @ts-expect-error
1620
['mixin1maybeDynamicNumber', maybeDynamic((arg) => arg, 1)],
21+
// @ts-expect-error
1722
['mixin1dynamic', maybeDynamic((arg) => arg, (props) => props)],
18-
['mixin2', mixin([
23+
['MIXIN_', css([
1924
['mixin2number', 2],
2025
['mixin2string', 'mixin2string'],
26+
// @ts-expect-error
2127
['mixin2boolean', true],
2228
['mixin2array', [{ scale: 3 }]],
2329
['mixin2object', { scale: 3 }],
30+
// @ts-expect-error
2431
['mixin2maybeDynamicNumber', maybeDynamic((arg) => arg, 2)],
32+
// @ts-expect-error
2533
['mixin2dynamic', maybeDynamic((arg) => arg, (props) => props)],
2634
])],
27-
['mixin3', () => mixin([
35+
['MIXIN_', () => css([
2836
['mixin3number', 3],
2937
])]
3038
])
@@ -36,7 +44,10 @@ describe('buildDynamicStyles', () => {
3644
['array', () => ([])],
3745
['simpleObject', {}],
3846
['simpleArray', []],
39-
['mixin1', () => mixin1],
47+
['MIXIN_', () => mixin1],
48+
['MIXIN_', () => null],
49+
['MIXIN_', () => undefined],
50+
['MIXIN_', () => false],
4051
])
4152

4253
expect(styles).toStrictEqual({
@@ -64,4 +75,41 @@ describe('buildDynamicStyles', () => {
6475
mixin3number: 3,
6576
})
6677
})
78+
79+
test('Should parse runtime styles', () => {
80+
const { css } = createStyled()
81+
const props = Object.freeze({
82+
isInitalProps: true,
83+
theme: {},
84+
})
85+
let styles = buildDynamicStyles(props, [
86+
['RUNTIME_', ['border', '2px solid white']],
87+
])
88+
89+
expect(styles).toStrictEqual({
90+
borderWidth: 2,
91+
borderStyle: 'solid',
92+
borderColor: 'white',
93+
})
94+
95+
styles = buildDynamicStyles(props, [
96+
['RUNTIME_', maybeDynamic((...args) => ['border', `${args[0]} ${args[1]} ${args[2]}`], '3px', 'dashed', 'yellow')],
97+
])
98+
99+
expect(styles).toStrictEqual({
100+
borderWidth: 3,
101+
borderStyle: 'dashed',
102+
borderColor: 'yellow',
103+
})
104+
105+
styles = buildDynamicStyles(props, [
106+
['RUNTIME_', maybeDynamic((...args) => ['border', `${args[0]}`], () => '4px')],
107+
])
108+
109+
expect(styles).toStrictEqual({
110+
borderWidth: 4,
111+
borderStyle: 'solid',
112+
borderColor: 'black',
113+
})
114+
})
67115
})

src/__tests__/buildPropsFromAttrs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { buildPropsFromAttrs} from '../styled'
1+
import { buildPropsFromAttrs} from '../buildPropsFromAttrs'
22

33
describe('buildPropsFromAttrs', () => {
44
test('Should combine passed props', () => {

src/__tests__/buildStyles.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { splitStyles, maybeDynamic, mixin } from '../styled'
1+
import { maybeDynamic } from '../maybeDynamic'
2+
import { splitStyles, createStyled } from '../styled'
23

34
type Props = any
45
type ResultWithFN = { fn: (props: Props) => unknown }
56

67
describe('splitStyles', () => {
78
test('Should split passed styles', () => {
9+
const { css } = createStyled()
810
const dynamicProp = maybeDynamic((arg) => arg, () => 0)
911
const { fixed, dynamic } = splitStyles([
1012
['number', 0],
@@ -14,21 +16,27 @@ describe('splitStyles', () => {
1416
['object', { scale: 1 }],
1517
['maybeDynamicNumber', maybeDynamic((arg) => arg, 0)],
1618
['dynamic', dynamicProp],
17-
['mixin1', mixin([
19+
['MIXIN_', css([
1820
['mixin1number', 1],
1921
['mixin1string', 'mixin1string'],
22+
// @ts-expect-error
2023
['mixin1boolean', true],
2124
['mixin1array', [{ scale: 2 }]],
2225
['mixin1object', { scale: 2 }],
26+
// @ts-expect-error
2327
['mixin1maybeDynamicNumber', maybeDynamic((arg) => arg, 1)],
28+
// @ts-expect-error
2429
['mixin1dynamic', dynamicProp],
25-
['mixin2', mixin([
30+
['MIXIN_', css([
2631
['mixin2number', 2],
2732
['mixin2string', 'mixin2string'],
33+
// @ts-expect-error
2834
['mixin2boolean', true],
2935
['mixin2array', [{ scale: 3 }]],
3036
['mixin2object', { scale: 3 }],
37+
// @ts-expect-error
3138
['mixin2maybeDynamicNumber', maybeDynamic((arg) => arg, 2)],
39+
// @ts-expect-error
3240
['mixin2dynamic', dynamicProp],
3341
])]
3442
])]

0 commit comments

Comments
 (0)