-
Notifications
You must be signed in to change notification settings - Fork 908
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
Add CSS Modules capability to vue components #13
Conversation
} | ||
}, function (window) { | ||
var module = window.testModule | ||
expect(module.data().styles.red).to.equal('_3iZItEFWW8HWfKNmM95gGD') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add 2 assertions here:
- the data should also contain the original
msg
field; - the style should have been inserted with the same class name. (possibly by selecting the first
<style>
node and do a regex match on its textContent)
Great work! I left a few comments, small things. LGTM after those are fixed :) |
I removed the newlines and added the assertions. Thanks for the hint! Could you think of an better way to inject the styles into vue? It would be great to have some way to specify if an object in new Vue({
data : {
styles : {
$$immutable: true,
[...]
}
}
}) Just to be a little bit more economical with the amount of bindings vue creates. [Edit] |
The cost of setting up a single watcher is relatively cheap in Vue, and once created it has no effect on other parts of the application, so I wouldn't worry too much about performance here. I've added support for frozen Objects in yyx990803/vue@9259325 , but using frozen objects in Another possible alternative is using a special syntax for local class names in templates, e.g: <div class:local="red"></div>
<!-- or -->
<div class=":local(red)"></div> Then, we need to retrieve the generated class names in the loader (by executing the css module inside the loader), with that we can rewrite all these local classes into the generated names. This would have 0 runtime cost and has a imo cleaner syntax. |
I like that but we still need the ability to access classes in js sometimes. Than it would definitely be better to allow the injection on a per component basis than globally on the vue-loader. |
Instead of adding the option to the webpack config, how about <style inject-locals>
/* ... */
</style> |
👍 I like that [Edit] |
I switch from the global option to the per style tag option like you suggested. To get this working I modefied the The only thing left would be to support some kind of template html preprocessors and inject the style object into that too. That would enable the 0 runtime cost of supporting CSS Modules. |
Cool, I think I've got it somewhat figured out. We just need to nail down the syntax. The tricky part is handling both global class names and local ones at the same time. Two possible options: <div class="global" class:local="local"></div> vs. <div class="global :local(local)"></div> I personally prefer the former a little bit, as it's more explicit and easier to parse/transform. |
The fist method would be definitely easier to parse but is kind of invalid html. I don't know if the html-parser used in this plugin would like that. I still like it that method a bit more too. |
It is valid html, as per the spec html attribute names can contain "all characters except tab, line feed, form feed, space, solidus, greater than sign, quotation mark, apostrophe and equals sign", and parse5 with htmlparser2 adaptor can handle these attribute names just fine. |
Oh, my bad good to know. Than everything looks good. Should this be added in this PR or should we create a new one for that? |
I'll flesh out the locals extraction/rewrite part a bit more, then we can look at how to move on from this PR. |
👍 Evan, if you are going with |
@azamat-sharapov that's a good question! |
So with af04dbb...master I've implemented what we discussed, with slightly different syntax: <style>
:local(.red) {
color: red;
}
</style>
<template>
<div local-class="red" v-local-class="red: showRed"></div>
</template>
<script>
module.exports = {
data: function () {
return { showRed: true }
},
created: function () {
// accessing local classes map from js
console.log(this.$localClasses)
}
}
</script>
|
Great work. So this PR can ne closed? |
Yes, although I feel bad that you're not getting credit for this (I worked on this when I was on the plane). Do you want to help with the docs? |
It's okay don't worry. |
So, after trying this out for a while, I somehow feel it's still a bit awkward to use, because essentially I have to write every class with Using local-by-default mode probably works better, but we can't turn it on by default, and it still has the same problems interacting with Instead, what I am trying now is auto-scoping: <style local>
h1 {
color: red;
}
</style> For style blocks with the <style>
#_3iZItEFWW8HWfKNmM95gGD h1 {
color: red;
}
</style> This gives the guarantee your local styles doesn't leak out, and the mental model is almost identical to traditional CSS authoring. One problem with this is that it doesn't prevent outside global class selectors from seeping in; However, if the whole app is written with vue-loader, this won't happen because you have control over what global classes are defined, and even in a team environment, you can avoid conflicts with good naming conventions (e.g. all global classes should start with |
There is a Post css loader that will make all styles local by default. See https://github.com/css-modules/webpack-demo |
I think the unique id has to be a class, so it can be used with components with multiple root elements. |
Evan, did you mean |
@FWeinb I'm aware of that, but having it being the default is a big hoop to jump through for new users. And as I said, it still causes extra care to be taken for @sjoerdvisscher that's a good observation. @azamat-sharapov yeah it's basically what you mentioned, but done automatically with the |
+1 for that |
It is already recommended to always use a root element. But you shouldn't really worry about the rendered markup if it makes your code easier to write and maintain. |
Yeah, you are right. Just thought how many things React adds to DOM behind the scenes.. It's more about maintaining. |
I just had some time to test the current implementation in a small project and can second @yyx990803 opinion now; |
I've implemented the new proposal (changed One small issue with What we do lose though, is the ability for components to securely avoid parent styles from seeping in. For example, say we have styles for So maybe for reusable 3rd party components, we still need the local-class syntax? Oh well, 3rd party components can get around this problem with naming conventions as well (e.g. prefixing all classes with a namespace). |
The gotcha for 3rd party components is kind of a killer, these kinds of bugs are very confusing and hard to debug. We want to make it easy for the user to write vue component but by introducing these subtle sources of bugs it can become very hard to reason about a component. Having to rely on some kind of "well written" 3rd party components is a very big burden. |
Well, CSS cascading problems have existed all the time, and it's not easy to solve (even from the Browser vendors perspective). Right now we have two ways to deal with it:
|
Turns out we can achieve CSS-in-JS inline styling (what most React components are doing) pretty easily: <template>
<div v-style="styles.button">hi</div>
</template>
<script>
var styles = {
button: {
color: 'red',
fontFamily: 'Source Sans Pro',
fontWeight: 'bold'
}
}
module.exports = {
data: function () {
return {
styles: styles
}
}
}
</script> Aha! We can translate CSS into these JS objects for the user so that they can write actual CSS! |
It will be really messy in my opinion.. there is big discussion/debate going on around web about "writing CSS in JS". I personally wouldn't use it, it's really like "react way" (they write HTML and now CSS in JS). Vue is completely different honey for me. That |
Well, the problem with |
I agree about standalone components. Have never developed standalone yet, but that's good info to keep in mind. Let's just leave standalone component makers to stick with v-style and |
That's what I'm thinking. If one really wants to ship a reusable-in-any-environment component, the choice is to either 1) use custom namespaced class names or 2) inline everything with |
Did you think about this a little bit more? I can see that there will always be a tradeoff in some kind but I would really like to see a viable solution in vue-loader. |
@FWeinb for now what we have is scoped styles (which can possibly affect child components). I'll come back to this when I get time, for now the focus will be shipping the router. |
Like requested in #12 I added support for including css via
css-loader
s css local scope feature to achieve true CSS Modules like described in this blog post.Technically this is done by injecting the result of the required CSS into the
data
object of each component, to gain access to the exported identifiers in the vue instance. Makeing this work required that you can't have multiple<style>
-tags when using this feature. I choose to implement it behind the runtime optioninjectStyle
.I don't now if injecting it into the Vue component is the most elegant way to do it but it is definitely working. It would be great if it where possible to have a object on the vue instance which will, by default, be a on time binding, without explicitly using
{{* }}
every time.