dparker2 / vue-emotion

Use emotion with Vue.js
MIT License
6 stars 1 forks source link

Scoped styles #8

Open Aaron-Pool opened 5 years ago

Aaron-Pool commented 5 years ago

I've been using the (now archived) version of vue-emotion by egoist for a while now. The three things that would really make me switch to this library would be:

1) an invisible implementation of targeting a child emotion component (but you already created an issue for that, so that's good đź‘Ť ) 2) Some sort of implementation that mimics scoped styles of vue style blocks. Because, at the point I am able to target emotion children components, I have to worry about styles leaking down through my component tree. 3) Switching to the styled wrapper component to be a functional component. I use these everywhere, so, the lighter the better.

dparker2 commented 5 years ago

Hey @Aaron-Pool thanks for the feedback!

Agreed on 1 and 3, I've got an idea of how to nicely do #1 so I'll likely be doing that this week/weekend.

I like the idea of 2, having an option to enable scoping would be great and consistent with vue, I'll have to play around and see if I can figure out the best way to do that.

Aaron-Pool commented 5 years ago

Sounds good! Let me know if you need a hand with anything. Been considering taking a crack a cleaner vue-emotion implementation for a while now. So I'm happy to pitch in if it helps move things along.

dparker2 commented 5 years ago

For the scoping, the expectation would be that the styles would be scoped to the elements in the same context that the styled component is used, and not leak into the “insides” of the styled component, right? Since css can’t go up, that means that it’s limited to slotted elements (default or otherwise).

So basically:

// Wrapper.vue
<template>
  <div>
    <span>Should not be red</span>
    <slot />
  </div>
</template>
// App.vue
<template>
  <styled-wrapper>
    <span>Should be red</span>
  </styled-wrapper>
</template>

<script>
import Wrapper from './Wrapper.vue'
import styled from '@vue-emotion/styled'

const StyledWrapper = styled(Wrapper)`  // somehow scoping enabled
  span {
    color: red;
  }
`

export default {
  components: {
    StyledWrapper
  }
}
</script>

I think because the applicable scope is basically the slots, it’s definitely doable, but I wanted to make sure my understanding of the use-case is correct?

Aaron-Pool commented 5 years ago

@ParkerD559

I've been thinking about this, and I think it essentially boils down to the fact that, with the way people use the styled components paradigm, the Vue SFC <style> block is typically eliminated, which removes the ability to specify <style scoped>. I think the most straight forward approach to implementing it for styled components would actually be to have a function you can call on your component definition export to add a scope attribute to every createElement call in the components render function (which would be compiled from the <template> tag. Then, for you styled component, you could just say something like .withScope(*ParentComponentName*) and it would append the [data-hash] attribute selector to all of it's style rules, just like the vue scoped styles implementation does.

For reference: https://vue-loader.vuejs.org/guide/scoped-css.html#scoped-css

Aaron-Pool commented 5 years ago

Possible api example

<script>
  import styled from '@vue-emotion/styled';
  import { scopedStyles } from '@vue-emotion/utils';

  const ParentComponent = scopedStyles({
    components: {
      StyledWrapper
    }
  });

  const StyledWrapper = styled(Wrapper)`
    span {
      color: red;
    }
  `.withScope(ParentComponent);

  export default ParentComponent;
</script>
<template>
  <styled-wrapper>
    <span>Should be red</span>
  </styled-wrapper>
</template>

Which would end up functioning as if it were:

<script>
  import styled from '@vue-emotion/styled';

  const ParentComponent = {
    components: {
      StyledWrapper
    }
  };

  const StyledWrapper = styled(Wrapper)`
    span[data-423sdfs] {
      color: red;
    }
  `;

  export default ParentComponent;
</script>
<template>
  <styled-wrapper data-423sdfs>
    <span data-423sdfs>Should be red</span>
  </styled-wrapper>
</template>
dparker2 commented 5 years ago

Btw, I went ahead and redid how the styling "works" so it now uses functional wrappers instead of mixins and regular components, and now you can target styled components -- not yet with object styles though. Going to start experimenting with the scoping stuff

Aaron-Pool commented 5 years ago

Great! Might play around with dropping it in place of the archived version of vue-emotion, see if it breaks anything.

dparker2 commented 5 years ago

add a scope attribute to every createElement call in the components render function

After some experimentation this really isn't possible because the scoped attribute needs to be added after all children are done rendering as well, so it's only possible to add it to the top-most element.

But! I went looking through vue-compiler-utils and vue/core/vdom, and I've kinda figured out how to get the scoping to work. Usage would involve adding a plugin, and then passing an option to styled, so:

import Vue from 'vue';
import { ScopedStyles } from '@vue-emotion/plugins';

Vue.use(ScopedStyles)
// App.vue
<template>
  <styled-wrapper>
    <span>Should be red</span>
  </styled-wrapper>
</template>

<script>
import Wrapper from './Wrapper.vue'
import styled from '@vue-emotion/styled'

const StyledWrapper = styled(Wrapper, { scoped: true })`
  span {
    color: red;
  }
`

export default {
  components: {
    StyledWrapper
  }
}
</script>

It would mimic how Vue does its own scoping (via postcss plugin and during vdom patching) by using a stylis plugin and a custom directive added by the render function (which has a hook for when vnodes are done being made). I think this is good because it actually allows you to specify scoped styles outside of a SFC, which is not possible with Vue anyways!

Aaron-Pool commented 5 years ago

Oh, I was actually talking about preprocessing the render function in the component definition as a string to add the scoping attributes (even a SFC has a render function generated from the given template), and then regenerating a new render function with the modified string

You're approach is perfectly sensible as well, I was just trying come up with a way to create the closest possible feature parity with <style scoped> functionality, but perhaps the added complexity isn't worth it đź‘Ť

dparker2 commented 5 years ago

So I hit a wall trying to get this to work in the scoped branch, I got it to work with default slots but not named slots (which apparently are mounted after directives hooks are run...) so I'm fairly stuck on this haha

Aaron-Pool commented 5 years ago

@ParkerD559 In Vue 2.6, all slot content became available on the $scopedSlots context property. ScopedSlots are compiled at a different point in the render lifecycle, which may solve you problem.

For reference:

  1. 2.6 Release notes (See the note on scoped slots under the "Performance Improvements" section)
  2. And see 2.6 note here