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

[RFC] Pluggable styling API #57

Closed
kettanaito opened this issue Aug 14, 2018 · 15 comments · Fixed by #262
Closed

[RFC] Pluggable styling API #57

kettanaito opened this issue Aug 14, 2018 · 15 comments · Fixed by #262
Assignees
Labels
enhancement New feature or request in progress
Milestone

Comments

@kettanaito
Copy link
Owner

kettanaito commented Aug 14, 2018

What

I propose to establishing a support for different CSS-in-JS libraries to help developers take benefits of Atomic Layout using their favorite styling solution.

Why

  • The library operates on CSS properties Object, which can be computed into string (when needed), and be compliant with about any modern CiJ solution

Implementation

To enable such support it has been decided to convert the library to a monorepo that consist of the following parts:

  • core library (@atomic-layout/core, responsible for media queries calculation, areas parsing, parametric components generation without attachment to any specific styling/rendering solution)
  • rendering libraries (i.e. @atomic-layout/emotion)

All rendering libraries would list @atomic-layout/core as a dependency and utilize functions and types the core library exports.

Usage

To use Atomic Layout with another styling solution (implying that such solution is supported) import the respective @atomic-layout/X package and use it with the same API described in the documentation:

import React from 'react'
import { Composition } from '@atomic-layout/emotion'

const MyComponent = () => (
  <Composition templateCols="250px 1fr">
    <span>Emotion support</span>
    <span>Example</span>
  </Composition>
)
@kettanaito kettanaito added the enhancement New feature or request label Aug 14, 2018
@kettanaito kettanaito added the help wanted Extra attention is needed label Nov 6, 2018
@kettanaito kettanaito changed the title Support Vue Modularize styling/rendering logic Jan 25, 2019
@bitttttten
Copy link

bitttttten commented Feb 27, 2019

Following from #122 I can think of 2 places where support needs to be added to allow the developer a possibility to configure things: how styles and class names are generated (a), and to configure which library is rendering the components (b).

#122 contains examples of a.

Just to generate some conversation, I will provide this example showcasing both:

diff --git a/src/components/Composition.tsx b/src/components/Composition.tsx
index c9905d3..dd9c7bf 100644
--- a/src/components/Composition.tsx
+++ b/src/components/Composition.tsx
-const CompositionWrapper = styled.div<CompositionProps>`
-  ${applyStyles};
-  display: ${({ inline }) => (inline ? 'inline-grid' : 'grid')};
-`
-
-const Composition: React.FunctionComponent<CompositionProps> = ({
-  children,
-  ...restProps
-}) => {
+const Composition: React.FunctionComponent<CompositionProps> = (props) => {
+  const {
+    children,
+    className,
+    inline,
+    ...restProps
+  } = props
   const areaComponents = parseTemplates(restProps)
   const hasAreaComponents = Object.keys(areaComponents).length > 0
   const childrenType = typeof children
@@ -40,11 +39,18 @@ const Composition: React.FunctionComponent<CompositionProps> = ({
   )
 
   return (
-    <CompositionWrapper {...restProps}>
+    <Layout.produceComponent.div
+      className={[
+        className, 
+        Layout.produceStyles(applyStyles(props)),
+        Layout.produceStyles`display: ${() => (inline ? 'inline-grid' : 'grid')};`
+      ].join(' ')}
+      {...restProps}
+    >
       {hasAreaComponents && hasChildrenFunction
         ? (children as ChildrenFunction)(areaComponents)
         : children}
-    </CompositionWrapper>
+    </Layout.produceComponent.div>
   )
 }

The component is moved inside render method, which is not good for performance. This was done so that the user can configure the library after imports, as it's confusing for the end dev has to run Layout.configure at a precise point in time (i.e. before importing any components).

If it's possible to keep the component back outside the render method whilst ensuring a good dev ex then that would be the approach.

@kettanaito kettanaito changed the title Modularize styling/rendering logic Pluggable styling API Mar 6, 2019
@kettanaito
Copy link
Owner Author

kettanaito commented Mar 6, 2019

@bitttttten Hi. I will try to put up an specification of the pluggable styling solution in the description above. Please, I would be thankful for any kind of input from your side!

The first question I wonder about is what is the signature of the produceStyles function? I drafted one on top, assuming it accepts a template literal (css string) and returns a generated class name. However, I see that styled-components doesn't have such API.

@kettanaito kettanaito changed the title Pluggable styling API [RFC] Pluggable styling API Mar 6, 2019
@kettanaito kettanaito added this to the 1.0 milestone Mar 6, 2019
@bitttttten
Copy link

bitttttten commented Mar 9, 2019

Yeah, I was thinking about styled-components and how their css api does not handle committing styles to the dom like emotion does. Here you can see my test of exactly this. More work needs to be done for styled-components.

And I was wondering about your original idea of having pre-defined plugins, especially for the use case of using a different library. I think it's pretty simple. For example, you could do:

import { Composition } from 'atomic-layout/emotion'

Which looks like:

import Layout from './Layout'
import css from 'emotion'

Layout.configure({
	produceStyles: (styles, ...rest) => css([styles], ...rest)
})

export default Layout
export { default as Box } from './components/Box'
export { default as Composition } from './components/Composition'
export { default as Only } from './components/Only'

Although this does not address any of other benefits, like decoupling atomic layout from a view library, having atomic layout emit CSS for dev's to handle at build time, and so on. It feels like inspiration should be taken from astroturf for that (not api design, but more the philosophy?).

(I am in the middle of a long weekend, so hopefully this comment is clear.. also I will probably come back with more feedback later on! 😛 )

@kettanaito kettanaito self-assigned this Apr 7, 2019
@kettanaito
Copy link
Owner Author

Hello, @bitttttten. I've established some changes to adopt pluggable styling solution under #145. May I please ask you to checkout that branch and see if it works with emotion for you? Your feedback is highly appreciated!

@kettanaito
Copy link
Owner Author

I've updated the Pluggable styling API request with the specification of the feature. It describes the feature behavior and changes introduces by the pull request.

@kettanaito kettanaito removed their assignment Jun 3, 2019
@hasparus
Copy link

Hey @kettanaito, I just wanted to notice that "SSR with no config" benefit that @bitttttten has mentioned in #122 is only present in @emotion/core and @emotion/styled.

BTW I just watched "Creating layouts that last". It's a great talk.


Q: Currently styled returns a React component. It would be nice to abstract from framework-specific return signatures, and operate with emitted class names.

Not every CSS-in-JS framework lets you operate on classNames. @emotion/core (Emotion for React) is an example.


I wanted to ask, since I experienced that migrations from styled-components to @emotion/styled are usually effortless. Does atomic-layout use any implementation details of styled-components? I'm wondering about just aliasing styled-components to @emotion/styled to adopt atomic-layout in projects that already have Emotion.

import styled from 'styled-components'

turns into

import styled from '@emotion/styled'

@kettanaito
Copy link
Owner Author

kettanaito commented Nov 13, 2019

Hi, @hasparus. Thanks!

Indeed, emotion and styled-components share a similar API, that's why emotion support is one of the first on the table to be adopted. Take a look at my comment in the related issue. It describes where styled-components are used. It may be also useful to read through initial emotion support thred.

TL;DR I think such support should happen, but it needs to be done properly. My main concern is how to build/distribute such variation of a library. For example, if you would like to use it with emotion, which package should be listed in peer dependencies? Both? It looks like it should be distributed as a separate @atomic-layout/emotion package to have a valid package.json. At this point I think that with a proper abstraction we can support about any styling solution, so perhaps the focus should go to such abstraction (the goal of this issue). Yet, I'm open to brainstorming and suggestions!

@hasparus
Copy link

hasparus commented Nov 13, 2019

wow! Thanks for quick response and for the link to your previous comment.

I think doing similar thing as rebass would be pretty great if it worked.

For example, if you would like to use it with emotion, which package should be listed in peer dependencies

I guess people would be fine without @emotion/core in peerDependencies. My reasoning behind it is: If someone is looking for a way to use atomic-layout with Emotion, they probably have Emotion already installed.

Swapping import name is a pretty brute way to go, and although it may be a quick win which allows allows introducing atomic-layout to 1.6m weekly downloads of @emotion/core userbase, it probably isn't the proper abstraction you are looking for.

@hasparus
Copy link

hasparus commented Nov 13, 2019

I think that with a proper abstraction we can support about any styling solution

Supporting styled-components-like styling solutions by injection of styled function seems plausible. It would allow you to cover styled-components, @emotion/styled and userspace derivatives (building a styled function for theme-ui wouldn't be too hard, and it would allow to use values from the theme as gutter for example).

I can imagine that other styling solutions (well, mby except styledjsx or linaria) could be supported with "support/plugin packages".

Do you have in mind a list of styling solutions you'd like to support with atomic-layout?

@kettanaito
Copy link
Owner Author

kettanaito commented Nov 14, 2019

That's a good reference from rebass, thanks! I reckon import replacement can be done via Rollup directly, but since Atomic Layout already uses Babel, it should be fine as well. The only question I have: where should this replacement happen? The end consumer cannot affect the bundled code, so I suppose it must be done during the library's build. Do you have an idea how the build pipeline would look like in this case?

I think such replacement must live close to the code base and be officially distributed. For example, I'd like to run end-to-end tests on atomic-layout+emotion/styled build to make sure it works per spec. Can we leverage certain CLI flag during installation and preinstall npm script to replace the imports?

My dreamland API of such dynamic styling solution would look similar to this:

import Layout from 'atomic-layout'
// import styled from 'styled-components'
import styled from '@emotion/styled'

Layout.configure({
  produceStyles: styled,
})

While this looks doable, in practice I've experienced that a part of Atomic Layout's code executes before this produceStyles is applied. If you are curious, you can experiment on the related feature branch in #145. This issue may also be related to how layout settings are distributed #209.

I would rather define which styling API Atomic Layout expects, than which libraries it supports. Since now we operate on styled-components-like API, I would stick with it for time being. Which makes styling support narrow down to the following solutions, initially:

  • styled-components
  • emotion/styled
  • arbitrary library with (css: string) => any signature

When such API expectations are established, we provide a developer with a contract, not a boundaries of a specific dependency. As long as the contract is fulfilled, Atomic Layout (its styling part) works. (Making styled function parametrized also solves the peer dependency issue, as now Atomic Layout won't require any styling peer dependency, but would require the productStyles to be set).

@kettanaito
Copy link
Owner Author

The more I think about the monorepo pattern the more it makes sense to be as a library's developer. When each variation of the library is a separate scoped package it can target variantion-specific implementation, dependencies, and test suites without creating a mess. As mentioned previously, the core of the library can be separated into atomic-layout/core quite easily, which is a great monorepo pre-reqiusite.

As the library's consumer, however, I feel struggle, as each library's vairant is a separate package. There has to be some naming convetion, like:

  • @atomic-layout/emotion
  • @atomic-layout/vue

Such imports are lengthy, and combined with the library's usage rate in a code base can become irritating quite fast. One can solve that with import resolve alias (in case of webpack), but that's not beginner-friendly in general.

@bitttttten
Copy link

@atomic-layout/emotion and @atomic-layout/vue look good to me! It also scales to native too with @atomic-layout/native.

FYI emotion follows this pattern:

import styled, { css } from '@emotion/native'

const Container = styled.View`
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 50px;
`

https://emotion.sh/docs/@emotion/native
https://emotion.sh/docs/@emotion/primitives

I have never used it myself, but the library also exposes a createEmotion function: https://emotion.sh/docs/create-emotion. Might be nice for some inspiration.

@kettanaito
Copy link
Owner Author

@bitttttten thanks for the feedback. The first step to support emotion is coming to an end, I'm yet to verify a few nuances and about to merge. The second step would be to create and publish the actual @atomic-layout/emotion package.

@kettanaito kettanaito mentioned this issue Dec 7, 2019
26 tasks
@kettanaito kettanaito self-assigned this Dec 7, 2019
@kettanaito kettanaito added in progress and removed help wanted Extra attention is needed labels Dec 28, 2019
@kettanaito
Copy link
Owner Author

kettanaito commented Feb 12, 2020

Official Emotion support

Atomic Layout now distributes a dedicated package that supports Emotion, called @atomic-layout/emotion.

$ npm install @atomic-layout/emotion @emotion/core @emotion/styled

@bitttttten
Copy link

Ahh you pulled it off! Congrats ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request in progress
Projects
None yet
3 participants