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

[Feature Request] Need for new option to manage css that is bound to a component. #580

Closed
ramakrishnamundru opened this issue Jan 17, 2017 · 9 comments

Comments

@ramakrishnamundru
Copy link

The vue-loader doesn't support the styling of dynamic content when used with scoped css.

#559

So I'm thinking that there should be another mode than scoped css to handle the css that can be applied to all the elements as long as the component is loaded.

if the styles are a part of the component then they will be applied only when the component is loaded.

This is extremely useful when using vue-router for changing views, so only the styles needed for the component are loaded and removed with the component.

example:

<template>
<root>
<content></content>
</root>
</template>
<style src="file.css" bound></style>

can be compiled to:

<template>
<root>
<content></content>
<link rel="stylesheet" type="text/css" href="link to scped css bundle">
</root>
</template>

This way it will be very useful to projects using jquery plugins or others where most of the content is generated dynamically.

And as it a build step I don't think it will effect the final build product.

@ramakrishnamundru ramakrishnamundru changed the title Need for new option to manage css that is bound to a component. [Feature Request] Need for new option to manage css that is bound to a component. Jan 18, 2017
@LinusBorg
Copy link
Member

From how I understand it, I don't think that this is a good solution.

If I understand the problem that you want to solve correctly, you are worried that styles that some 3rd party plugin, e.g. a jQuery Plugin, uses, would conflict with other CSS rules. Is that correct?

In that case, your solution makes that a bit more unlikely, but only insofar as the styles are gone when the component is not used.

there will inevitably be cases where this component (which uses your above feature), and some other component, which has CSS rules that conflict the the ones of the plugin, will both be in the document at the same time.

https://jsfiddle.net/Linusborg/2tvttm8a/

So this seems to add a false sense of security, and as soon as the above situation occurs, you will be wondering why sometimes the styles are ok, and sometimes they are not.

I think a far better approach would be to tackle this hands on and include the styles always - the sooner you find you that something is clashing, the better.

@ramakrishnamundru
Copy link
Author

include the styles always

This is not an option for me because of the size of my project if the css of all plugins is included the page will become heavy.

@kazupon made it clear that it is not possible to add scopeId to dynamic content, so the solution is to daclare them globally,

another case is that I need to apply some styling to body like background-color e.t.c.. when a specific route/component is loaded.

eg- main layout--normal body
another layout like special pages like login section-- body has styles like bg-color, overflow e.t.c..

the way I see it there is no way to do this with scoped css.

and if the styles are given globally then they stay and apply to the body even after the route/component is changed.

that's why I think there should be a way to remove styles.

So this seems to add a false sense of security.

As this is going to a new option the consequences can be clearly documented and made clear to any one who decides to use this option

@LinusBorg
Copy link
Member

LinusBorg commented Feb 4, 2017

This is not an option for me because of the size of my project if the css of all plugins is included the page will become heavy.

In that case, just use webpack code splitting. Wrap the plugin's css and js in a module and lazy-load that in your component when you need it.

Then the css will be loaded only when needed, but stay in the page (not a problem in my book)

another case is that I need to apply some styling to body like background-color e.t.c.. when a specific route/component is loaded.

eg- main layout--normal body
another layout like special pages like login section-- body has styles like bg-color, overflow e.t.c..

That should be solved by adding/removing classes, not loading/ unloading css from a component. Css in vue files is meant for those components only.

@ramakrishnamundru
Copy link
Author

In that case, just use webpack code splitting. Wrap the plugin's css and js in a module and lazy-load that in your component when you need it.

Currently doing that. that causes some problems.

if css in page a overrides css in page b:

if page b loads first everything is okay, But if page a loads first and then page b then page b is broken due to css override.

Then the css will be loaded only when needed, but stay in the page

that staying on the page is my problem.

Css in vue files is meant for those components only.

the way i want is the routing work like any normal html site,

the css in that page is only for that page and no other styles to worry about,
but with vue-loader when global styles remain they are bound to override some other styles in some other pages.

@LinusBorg
Copy link
Member

  1. This is not the same problem.
  2. Git hub is not a support forum. Advice is given at forum.vuejs.org

@jathpala
Copy link

jathpala commented Jul 9, 2017

I think the original poster was not so much talking about styles from external libraries conflicting with other styles, but rather applying styles from within the .vue file to dynamically created DOM nodes within the template.

See the following as an example case:

<template>
<div id='parent'>
    <h1>Scoped Style Test for Dyanmic Components</h1>
    <p>This is a static paragraph</p>
</div>
</template>

<script>
export default {
    name: "test",
    mounted() {
        let node = document.createElement('p');
        let text = document.createTextNode('This is a dynamic paragraph');
        node.appendChild(text);
        document.getElementById('parent').appendChild(node);
    }
}
</script>

<style scoped>
p {
    font-size: 3em;
}
</style>

Please see the full gist.

The intention here is that all paragraphs within this component have a font-size of 3em. This is true for the static paragraph within the template. The second paragraph (created from the mounted() function) will not have this style applied.

The reason for this is as part of the css compilation the css selector p is converted to p[data-v-xxxxxxx] but the data-v-xxxxxx attribute is only added to the static elements. One way to fix it would be to change things so instead of p[data-v-xxxxxx] the selector is converted to [data-v-xxxxxx] p. This would have the same effect of limiting scope but would apply the formatting to all p elements added within the component in the future. I'd be happy to help create a patch for this but I can't quite work out exactly where the p -> p[data-v-xxxxxx] translation is going on.

There are two things that would complicate this solution. Firstly, the style for the root node still needs to be applied as div[data-v-xxxxxx] rather than [data-v-xxxxxx] div and so we'd need to test for the root node specifically.

Secondly, it's not clear how this would work with components composed in via slots. Do they inherit the styles (as they would if this was just straight html; this is what would happen with this implementation) or should they not be affected by styles in their parent component (might be harder to do with this approach).

The workaround is not to use the scoped attribute in which case everything will work fine in this component. The problem is however the styling will leak out into the rest of the system and all p elements will have a size of 3em. To avoid this, styles could be applied based on a class rather than the element and a class name chosen in such a way that it would be unlikely to conflict with any class names outside of the component (e.g. by using the BEM naming convention).

@LinusBorg
Copy link
Member

For elements that are created dynamically by the dev with them DOM API instead of letting Vue handle this, I think the solution is pretty straightforward: get the data-v-* attribute from the component's root element and apply it to all dynamically created elements.

Since these elements are handled outside of the dom that Vue controls, I don't think vue-loadet should have any responsibility in their styling.

@jathpala
Copy link

Fair enough I guess. The main drawback having to manually add the data-v-* attribute is it makes it harder to use third party non-vue libraries that do DOM manipulation. I can understand the point that because these work behind Vue's back you may explicitly not want to support them natively but rather require some developer workaround.

As I mentioned above, simply leaving out the scoped attribute is probably the easiest way though it does potentially allow styles to leak out of your components.

The recommendation to manually add the data-v-* attribute is probably the best method to preserve component encapsulation but will require some extra coding, which for third party libraries would require some understanding of their implementation details potentially.

Just as an example of how the manual data-v-* workaround could be implemented, I've modified my component above such that it should now work with the scoped attribute. (I've used jQuery $.each() in my code but I'm sure it could be done easily enough in vanilla js).

<template>
<div id='parent'>
    <h1>Scoped Style Test for Dyanmic Components</h1>
    <p>This is a static paragraph</p>
</div>
</template>

<script>
export default {
    name: "test",
    mounted() {
        let node = document.createElement('p');
        let text = document.createTextNode('This is a dynamic paragraph');
        node.appendChild(text);
        
        // Support scoped style in vue loader
        let re = /^data-v-\w{8}$/
        $.each(this.$el.attributes, (k, v) => {
            if (re.exec(v.name))
                node.setAttribute(re.exec(v.name)[0], "");
        }

        document.getElementById('parent').appendChild(node);
    }
}
</script>

<style scoped>
p {
    font-size: 3em;
}
</style>

@yyx990803
Copy link
Member

Closing outdated issues - this should be solvable with /deep/ or >>> selectors in the latest version.

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

No branches or pull requests

5 participants