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

Emotion Primitives #658

Merged
merged 102 commits into from
Jul 6, 2018
Merged

Emotion Primitives #658

merged 102 commits into from
Jul 6, 2018

Conversation

nitin42
Copy link
Contributor

@nitin42 nitin42 commented May 14, 2018

What: Add emotion-primitives to emotion monorepo

Why:

How: Integration with React primitives

Checklist:

  • Documentation
  • Tests
  • Code complete

TODO

  • Render primitives on Web
  • Add all emotion features like passing props to interpolate functions, withComponent and etc for React Native and Sketch
  • Add tests
  • Improve error warnings
  • Build
  • Flow types
  • Renders on Web ✅
  • Renders on Sketch ✅
  • Renders on Native ✅

Once this is done, we can extract parts for React Native and create a separate package emotion-native.

@nitin42 nitin42 changed the title Init commit Emotion Primitives May 14, 2018
render(<Title>Hello</Title>)
*/
export const emotionPrimitive = tag => {
if (Platform.OS === 'web') {
Copy link
Member

@Andarist Andarist May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho this can be easily split at build time - we can create 2 separate bundles and point from package.json with main/module and react-native keys appropriately

I can help with setting this up later

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be great.

}

export function splitProps(rootEl, props) {
const rest = { ...props }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will make a copy of props, seems redundant as u only read from it anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha ya I think I forgot to refactor before pushing the code. We can remove this since 'props' is pass by value.

export function getPrimitiveProps(element, props) {
const acc = {}

Object.keys(props).forEach(prop => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iteration style seems inconsistent with splitProps where for similar job you use .reduce

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will change in next commit. I was testing some weird behaviour caused due to primitive props like onLongPress.

@tkh44
Copy link
Member

tkh44 commented May 15, 2018

@nitin42 I believe you can use this as an example to create the styles.

https://github.com/emotion-js/next/blob/master/packages/serialize/src/index.js#L66

https://github.com/emotion-js/next/blob/032eab919602eca87165e92b0546bb76f95a0733/packages/css/src/index.js#L4

@nitin42
Copy link
Contributor Author

nitin42 commented May 15, 2018

Thanks @tkh44 ! Got it working. Wrapping up the work now by preparing docs, examples and demos for all the platforms (native, web and sketch).

@@ -0,0 +1,58 @@
import React from 'react'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import * as React from 'react' works better with typings

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take care of adding flow types in next few commits!

@@ -0,0 +1,58 @@
import React from 'react'
import transform from 'css-to-react-native'
import cssToObjects from 'css-to-object'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably should be cssToObject

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not using this anymore! Haha It's better if you don't review this now 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many things are going to change 😅

// Resolve the style using StyleSheet.create
rnStyles = resolveStyles([transform(cssToRN)])
} else {
rnStyles = [...styles]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think spread in general has some extra work to do (like dealing with array holes, using iteration protocol etc), I guess this is known to be an array so styles.concat() should be slightly more performant

Copy link
Contributor Author

@nitin42 nitin42 May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! We can definitely make changes and add bits related to performance. Since that is emotion's trademark 😉

@nitin42
Copy link
Contributor Author

nitin42 commented May 15, 2018

Thanks @Andarist for the initial review. Now I know the contributing parts a little better. Stay tuned for some surprise. This requires some more work but it should be stabled by next week.

css = cssToObjects(styles.join(''))

Object.keys(css).forEach(prop => {
cssToRN.push([prop, css[prop]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cssToRN is only manipulated here and passed as argument to a function just few lines below, as it's size is known (Object.keys(css).length) it might be worth to preallocate it based on that

I mean smth like this:

const keys = Object.keys(css)
const cssToRN = new Array(keys.length)
keys.forEach((prop, i) => {
  cssToRN[i] = [prop, css[prop]]
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm! Makes sense. But what about Array instantiation cost ?

// Collect valid style props for the primitive
const { styleProps } = splitProps(tag, this.props)
// Collect valid props for the primitive
const primitiveProps = getPrimitiveProps(tag, this.props)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getPrimitiveProps returns fresh object, would be better to reuse it as props which get passed down instead of creating a second object immediately by spreading it

I mean this:

const primitiveProps = getPrimitiveProps(tag, this.props)
primitiveProps.style = getStyles(rnStyles, this.props, styleProps, this.context)
primitiveProps.children = this.props.children
return React.createElement(primitives[tag], primitiveProps)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be merged to splitProps instead. Like this -

const { styledProps, primitiveProps }  = splitProps(tag, this.props)

@@ -0,0 +1,3 @@
import { emotionPrimitive } from './emotionPrimitive'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u can shorten it to just

export { emotionPrimitive } from './emotionPrimitive'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it seems that u should export a default here (instead of named export) with

export { emotionPrimitive as default } from './emotionPrimitive'


function isValidStyleProp(element, propName) {
if (element === 'Text') {
return textStyleProps.includes(propName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

includes requires polyfills to be used by library's users if they want to support older browsers, not sure if it's worth using in that case as indexOf equivalent is just a little bit more verbose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree! I will replace this with indexOf > -1

@SimeonC
Copy link
Contributor

SimeonC commented May 16, 2018

Just tagging #119 and #141 to link this PR to the two relevant issues to save some someone else search time 😄

@nitin42
Copy link
Contributor Author

nitin42 commented May 16, 2018

Almost done! 😅 It's ready to be reviewed.

What was changed?

Added new implementation for styled component. Overall, the core was refactored.

Remaining

Exposing platform specific API and bundling accordingly (@Andarist @tkh44 I need some help over here)

@Andarist
Copy link
Member

Exposing platform specific API and bundling accordingly (@Andarist @tkh44 I need some help over here)

Please make sure that you have checked "Allow edits from maintainers." checkbox here - I will try to find some time to add needed changes to this PR tomorrow. This doesn't have to block this PR though - it's just a small optimization that can be done.

@nitin42
Copy link
Contributor Author

nitin42 commented May 16, 2018

Cool! I was thinking of removing react-emotion first from this package but realised that may be we can bundle accordingly! Thanks.

if (stringMode === true && strings[i + 1] !== undefined) {
styles += strings[i + 1]
}
}, this)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldnt arrow function be good enough here? passing this like that is a little bit surprising for the reader at the first moment

Copy link
Contributor Author

@nitin42 nitin42 May 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was to me too when I first referenced this with emotion/css. We can change this, no issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this to save a few bytes so that Babel didn’t need to add var _this = this. It won’t make a big difference but it helps. I’m fine with changing it though

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if that's the case I'm fine with keeping it here too

}

function convertStyles(str) {
if (typeof str === 'string' && str.length === 0) return str
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could return '' here, i doubt it is any different than returning str here which is an empty string in that case and making it more explicit would help recognizing the intention a little bit sooner (not that it's particularly hard to see it in current form, but still 🤷‍♂️ )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, sure!

// Get prop name and prop value
const ar = style.split(': ')

if (ar[0] && ar[1]) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both need to be coerced to booleans, we could make this more explicit by checking ar.length === 2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. Earlier I was checking if both were not undefined!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how those elements could be undefined? checking against .length here is preferable as we should avoid out of bounds accesses

import { css } from './css'

function isPrimitiveComponent(component: React.ElementType) {
switch (component) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is switch case going to be faster than indexOf or object property lookup?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. switch cases are realllllllllllllly fast.

Copy link
Contributor Author

@nitin42 nitin42 Jul 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be object property lookup!?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't do a object property lookup since our keys are functions but switch cases are wayyyyyyyyyyyyyy faster anyway. https://esbench.com/bench/5b3f224bf2949800a0f61e14


return React.createElement(
component,
pickAssign(pick, {}, props, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pick -> pickTest?

this.setState({ theme })
}

export const testAlwaysTrue = () => true

export const pickAssign = function(testFn, target) {
export const pickAssign: (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be good to package those common utils at some point

buffer += interpolation
export function css(...args: any) {
let vals
styles = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could maybe reuse styles array instead of creating a new one that has to be GCed?

Copy link
Member

@emmatown emmatown Jul 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's really worth it/will make a difference. We create a bunch of arrays since css-to-react-native accepts styles like this [['color', 'hotpink'], ['background-color', 'yellow']] so I don't thinking creating one less here will matter. Also, I don't think mutating it is a good idea since StyleSheet.flatten could have memoization/could have it in the future and using the same array would break that.(that might never happen but in general I think passing an object to a function that we don't control and mutating it isn't a great idea)

@@ -3,71 +3,82 @@ import transform from 'css-to-react-native'
import { StyleSheet } from 'react-primitives'
import { interleave } from './utils'

export function css(...args: any) {
let vals
let styles
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, i guess this is most efficient way to do this, but this code (as well as similar code in original emotion) is really hard to follow because of this leaking state, maybe we could at least comment those lines here (state declaration) and its initialization in css?

@nitin42
Copy link
Contributor Author

nitin42 commented Jul 6, 2018

Did f8e6958 break anything locally ?

@emmatown
Copy link
Member

emmatown commented Jul 6, 2018

Yeah, one sec, I'll fix it.

@nitin42
Copy link
Contributor Author

nitin42 commented Jul 6, 2018

Sorry I accidentally used git alias on my system instead of pulling changes from the remote branch emotion-primitives 😅 Thanks!

@emmatown
Copy link
Member

emmatown commented Jul 6, 2018

All good!

I did that on master a few days ago 😂

@emmatown
Copy link
Member

emmatown commented Jul 6, 2018

I'm gonna rename this to @emotion/primitives, merge and release this tomorrow unless anyone has any objections.

@emmatown emmatown removed the WIP label Jul 6, 2018
@emmatown emmatown merged commit 09c84d6 into emotion-js:master Jul 6, 2018
@emmatown
Copy link
Member

emmatown commented Jul 6, 2018

Just published @emotion/primitives 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants