Vue: HTML case sensitivity workaround

Created on 6 Feb 2016  ·  48Comments  ·  Source: vuejs/vue

The Problem

So as we all know, HTML is case insensitive. myProp="123" gets parsed as myprop="123" and this has led to the caveat in Vue.js where you have to use my-prop="123" to refer to a prop declared in JavaScript as myProp. This bites beginners quite often.

In addition, we also need to apply the same mapping to custom components - e.g. when you define a component:

import MyComponent from './my-component'

export default {
  components: {
    MyComponent // es2015 shorhand
  }
}

You must use <my-component> in the template instead of <MyComponent>.

The annoying part here is because Vue.js relies on the browser to pre-parse the templates, by the time Vue.js gets to compile it, the case information is already lost.

The Idea

What if we adjust the matching logic so that things would just work? For example, making this possible:

<MyComponent :myProp="msg"></MyComponent>

Why?

In addition to eliminating the camelCase vs kebab-case inconsistency in our code, there are a few practical reasons why we would prefer PascalCase/camelCase for components and props:

  1. Props need to be referenced in templates and JavaScript as properties. Having hyphens in them makes it very awkward. (myProp vs. this['my-prop'])
  2. When we import another component, the variable name cannot be kebab case. e.g. You can do import MyComp from './my-comp' but my-comp is simply not a valid variable name. And with the ES2015 object literal shorthand you can just do components: { MyComp }.
  3. Capitalized component names stand out more vs. regular elements, making it clearer what tags are custom components and what tags are not.

Technical Details

The underlying implementation is that when we process the props and component options, we normalize them into lowercase. This way, they simply become mycomponent and myprop during internal matching process, but you can still use the desired case in your app code. (In fact users don't even need to know about these internals)

Potential concerns:

  1. Backwards compatibility. The lowercase conversion can be done alongside the current kebab-case conversion, so both syntax can co-exist, nothing will break.
  2. myProp and MyProp will be treated as the same thing in the template. However, it doesn't make any sense to have two props or two components in the same component differentiated only by case, and we can easily detect and warn against such usage.
  3. Should we apply the same rule to custom events as well? For example:

html <MyComponent @myEvent="handleIt"></MyComponent>

This basically means event names become case-insensitive, which has a bigger implication than props and component names because this affects event system usage in pure javascript. Does it make sense to normalize all event names into lowercase? Again, it seems rare to have two events differentiated only by case (e.g. having both myEvent and myevent in the same app that do different things), but I do want to get feedback on this.

discussion

Most helpful comment

The one rule to commit to memory: HTML = kebab-case, JavaScript = camelCase

By HTML I mean attributes and tags. Attribute values are JavaScript expressions when you use v-bind, so the second statement applies.

All 48 comments

<MyComponent :myProp="msg"></MyComponent>

+
I often want to write that way to see difference between the components and tags

+1

Does it make sense to normalize all event names into lowercase?

Yes! It makes sense, because it makes code more readable. I always keep event names lowercase, separate them by dash, instead of camelcase. I think adding warning for camelcase event names would be also good.

Does this means that Vuejs will be moving away from the HTML spec pursing a similar approach like Angular?

Is there some performance concern?

A couple thoughts on the potential concerns:

  1. As long as backwards compatibility exists I'll be cool with whatever is ultimately decided, but will probably be continuing using the kebab case method
  2. The lack of distinction between camel-case and lower camel-case could be confusing for some. Generally speaking var CamelCase would refer to a class and var camelCase would refer to a non-class variable, var camelCase = new CamelCase();. But, I don't think this would be an issue, because you wouldn't want to be creating classes that were named after your components.
  3. I would agree that creating two unique events only distinguished by the case would be very poor programming.

My biggest concern is introducing weird inconsistencies with how people code. For example, all of these are valid and identical: :myprop="" :myProp="" :mYpRoP="" :my-prop="".

👎 Keep kebab-case in the markup and camel case in the code. It's part of the HTML spec and ignoring case will be a greater learning curve for those coming from other frameworks or who have already learned the standard.

I agree with @Teevio, consistency will be lost

HTML uses kebab-case & it's an accepted community standard that ECMAScript is a camelCase language. We should keep them separate instead of _hiding_ (In react the only way you can get react to render custom attribute is via data-* & aria-*, which enforces consistency).

Explaining why (say via MDN link or even here) camelCase <-> kebab-case to a _beginner_ will greatly help the beginner's HTML understanding.

Agree with Evan, readability and code consistency is more important!
+1

To me it will look really weird to have camelCase inside HTML.

HTML is HTML, JS is JS

current version is just fine

Having been using Vue for 6 months, that - still gets me everytime :( not a big deal as the warning for Vue are so good I know what I did, but I can fully understand the idea here, and support it +1

+1 Backwards compatibility too.

+1
I agreed. we need to keep the backwards compatibility.

Does it make sense to normalize all event names into lowercase?

I agree with @azamat-sharapov

@Teevio Vue components are roughly classes: when you do var MyComp = Vue.extend({ .. }) you can then do var myComp = new MyComp(). As for the multiple valid syntax issue, it already exists: :my-prop, :MY-PROP and :mY-pRop all work the same as of now, because HTML simply throws away all case information. It's not much different with the proposed feature. Like with all style arguments, it's all about picking a style and stick with it.

Re @jamesxv7 @moe-szyslak @jonagoldman and others that have concerns about moving away from HTML standard: it's totally valid to write camelCase tags/attributes in HTML, it's just the tag/attribute name matching will be performed in a case-insensitive manner. This is what the spec says:

In documents in the HTML syntax:

Tag names for HTML elements may be written with any mix of lowercase and uppercase letters that are a case-insensitive match for the names of the elements given in the HTML elements section of this document; that is, tag names are case-insensitive.
Attribute names for HTML elements may be written with any mix of lowercase and uppercase letters that are a case-insensitive match for the names of the attributes given in the HTML elements section of this document; that is, attribute names are case-insensitive.

So, if using PascalCase/camelCase improves code consistency/readability, it's totally spec-compliant to do so. This may not be for everyone, but if you prefer kebab-case you can keep using that.

And in particular, re @jamesxv7: this is different from what Angular 2 is doing. Angular 2 is making their templates case-sensitive by introducing a customized HTML parser. On the contrary Vue actually follows the spec by making the JS counterparts case-insensitive.

I prefer keeping kebab in html. I like the idea of consistency but I like the idea of spec compliance more.

Found camelCase tag: . HTML is case-insensitive. Use instead. Vue will automatically match it against components defined with camelCase ids in JavaScript.

This warning is already pretty concise as to how component registration works. That said, as others have mentioned, I think allowing camelCase in HTML is great, as long there's still the option to continue writing kebab.

@yyx990803 yah, agreed. Just trying to think up as many arguments as I could against it, but honestly I don't have any that would stick. Like you mentioned, at the end of the day we're arguing stylistic choices. I personally think that as long as we can stick with what we already have while having the option to use the new stuff (but not forced to) I'm cool with the changes mentioned.

If kebab-case still works and using camelCase/ PascalCase are an option that don't break BC, when I use them, then I can't be against the addition. It isn't a change that forces me to do something different. It's just a new option.

The only thing I could say or suggest is to make sure this option is well documented and - Good work, as always Evan!

Scott

Maybe we can make an option out of it: warning or ignoring.
Say I have a component: myComponent
and I refer to it in html as mycompOnent I personally would prefer a warning, like:
we found something that would match "myComponent": "mycompOnent" (line 152), use "my-component" instead
The same for props of course.
I think later-on-readabilty is more important than very thing working on the first try.

I also stumbled upon kebab-case/camelCase problems. But the real problem was I got no warnings what was wrong ;)

Default could be that there is no warning and it just works, that's irrelevant for me.
Also the warnings should be visible only in debug mode I think

What about things like atone vs a-tone vs at-one? I imagine they are quite rare occurrences though.

@simplesmiler kebab case props would still be matched with case-sensitivity using old rules.

This doesn't promote transparency to web standards. The custom elements spec states that names should contain a hypen: <my-component/>

What about what @simplesmiler said: addOne and adDone would execute the same code. This would be especially nasty for the implementation for events,

And because html is case insensitive, lets not introduce the idea of casing from the library. This implementation would only promote casing in html, which in my opinion is a bad idea.

Also, we use hypen separation in file names. Should we remove them there as well, and start adding casing?

Lastly: having the hypen & casing system co-exist promotes different coding styles for new developers on a team.

I prefer @paulpflug's approach; Proper warnings in this area would help a lot.

I'm not a fan of making the HTML Pascal/Camel case. It breaks web standards, I know it's nice to keep consistency.

By trying to make things the tiniest bit consistent you add another layer of complexity. It may also be enticing bad practices. A library should promote staying to the standards and not mislead developers, as one day they may have to work in a place not using Vue, resulting in not understanding why the HTML is being parsed differently.

I totally agree with @paulpflug: Adding a warning means less work for production code and puts developers back on track to writing valid code.

A good argument case as to why this shouldn't be implemented: http://eisenbergeffect.bluespire.com/on-angular-2-and-html/

This is commonly a highlighted reason people dislike Angular 2. I agree totally with keeping libraries conforming to standards. It was once drafted for HTML to be case sensitive, and it was thrown out because of too many issues and opening up situations of too much flexibility.

@blake-newman: Regarding this, I think @yyx990803 already talk about it in a previous comment.

@jamesxv7 That comment sums it up pretty well; Evan is not proposing changing HTML spec, he's proposing changing how Vue locates component names. Rather than convert kebab to camel and finding the matching component it would probably strip dashes (to accommodate kebab) and then search components with no case sensitivity. The HTML itself will continue to be spec compliant. It also would allow us to use whatever case we want. This seems like not an evil or bad choice to me :)

@yyx990803 do you plan the <MyComponent> style to be the promoted style (i.e. docs and examples will be written like this), or it will be just an option, and kebab-case style will remain the primary one?

@blake-newman do read this comment - it does conform to the standard :)

@paulpflug @guidobouman : there already are warnings for camelCase tags and attributes if you are using latest versions of vue-loader or vueify. However, the camelCase checks must be performed at compile time because at runtime the case information would have already been lost due to the HTML parser behavior. So if you are using Vue without vue-loader or vueify, there won't (and can't) be any warnings.

@yyx990803 - But, the spec @blake-newman linked to for web components does state this:

The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production, must contain a _U+002D HYPHEN-MINUS character_, and _must not contain any uppercase ASCII letters_.

I am just not too sure how that relates to Vue components. In the docs, you do say, you try to loosely follow the web components standard.

You may have noticed that Vue.js components are very similar to Custom Elements, which is part of the Web Components Spec. In fact, Vue.js’ component syntax is loosely modeled after the spec.

So, I'd say the spec needs to change first, in order to allow camelCase and PascalCase.

Scott

@smolinari the Vue docs say that it is 'loosely modeled' not 'strictly' and in my mind that leaves room for this change.

@yyx990803 the case information may be lost, but there could still be a warning.
When I wrote 'mycOmponent' in the template it will be parsed to mycomponent but expected is my-component then Vue (in debug mode) should look for mycomponent besides my-component and warn me about the wrong usage. The lost case information doesn't matter here.
There could be an option to suppress the warning and match directly instead (equals your suggested behavior).

-1 to migrating to camelCase/PascalCase. It would be somewhat jarring to see JS-like syntax in HTML. Same reason why I cannot stand jsx.
+1 to @paulpflug's suggestion. If the problem is onboarding beginners, why not just issue a warning that informs the user of the problem?

@paulpflug that does sound like a valid idea!

I agree, having a warning that says 'mycomponent' is missing, did you mean 'my-component'? feels better than silent substitution.

@yyx990803 Is it possible to do this on a global option api?
eg
Vue.config.kebab = true (by default) -> <my-component :my-prop="msg"></my-component>
Vue.config.kebab = false -> <MyComponent :myProp="msg"></MyComponent>

@yyx990803
just wondering, what is the ideal we are striving for?
as @rpkilby said, <MyComponent myCustomProp="myProp" data-name="prop" aria-label="Close" onclick="myDialog.close()"> looks weird.

Essentially, the problem exists because js and html are different technologies and use different naming systems. And using same case(kebab or camel) in both technologies will shift weirdness from one place to another but the underlying problem will persist
So I believe, best we can do is draw a line. and the current line i,e. kebab case in html context and camleCase (and PascalCase) in js context is very good.

so IMO, we should just support current conventions instead of looking for a better one. And ofcourse, use warning to help beginners

@prog-rajkamal yeah, I'm now inclined to just implement the warning.

I now vote :+1: on just adding the warning too.

Scott

:+1: for adding a warning

:+1: for the warning too

Closed via ccf9bede6bc39fb62e43e1efe98136c5f35dae6b & d7b55c4ee8491dbd853e28a1527050d2d1e7deab

A warning would be great. I just spent an hour trying to figure out why my custom event wasn't be responded to (the answer being it had a camel cased name).

There's a warning when you have a corresponding component or prop that would respond to the kebab-case version of your PascalCase component or prop. If you make a typo in an event there's not much Vue can do about that.

Or do you mean for default existing event props like v-on:keyup or @keyup in short?

@guidobouman in my template file I had <my-component v-on:customEvent="myMethod">. In a child component I had this.$emit('customEvent');. The actual event being listened to by the parent is customevent not customEvent, of course, but it took me ages to figure that out because it's not easy to debug. I was thinking it would have been good to warn that a camel case attribute would not be parsed as such, for forgetful folk like me. Perhaps this has already been discussed above, and if so I apologize.

@anthonygore unfortunately it's impossible, because browser converts html to lowercase before Vue has a chance to access it.

My question is, why does Vue cannot convert it for us? Why do we have to remember about kebab-case in