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

Naming in PascalCase or kebab-case #1162

Closed
myst729 opened this issue Oct 1, 2017 · 14 comments
Closed

Naming in PascalCase or kebab-case #1162

myst729 opened this issue Oct 1, 2017 · 14 comments

Comments

@myst729
Copy link
Contributor

myst729 commented Oct 1, 2017

@Jinjiang @chrisvfritz @Justineo

Regarding the discussions in PR #1160 , I have different opinions.

HTML tags are case insensitive, then PascalCase in Vue template has no difference from "single word", which is considered bad practice. You may argue "It's Vue template, not HTML", then what's wrong with a single word? The explanation is not quite compelling either. PascalCases are for constructors, or classes. Instances would adopt camelCase (evil eyes here).

It's true that Vue template is not HTML. But it borrows HTML a lot, and in the end will be compiled into HTML. So conceptually, Vue template is deeply coupled with HTML. That's the reason I prefer kebal-case - the HTML custom elements syntax.

@Justineo
Copy link
Member

Justineo commented Oct 1, 2017

OK let's continue the discussion in this issue.

Some earlier discussion can be found under #1160.

I think kebab-case is also clear to identify components from elements because elements always have a one-word name while component names always have more than one word. PascalCase might not be the only way to make the visual difference.

According to current style guide:

For component names:

Scenario Convention
template and <template> in SFC PascalCase
In-DOM templates kebab-case
JSX PascalCase

For props:

Scenario Convention
template and <template> in SFC kebab-case
In-DOM templates kebab-case
JSX PascalCase

The logic here is not quite consistent IMO. As @chrisvfritz said earlier:

PascalCase is a convention used to identify instances in JavaScript. It applies to components, because they're really both JavaScript and instances. I think PascalCase would be strange for attributes, because they don't have instances.

  1. Instance are identified by camelCase
  2. Properties of JavaScript objects are in camelCase

So I think JavaScript “semantics” doesn't apply here. As the explanation of kebab-casing for prop names in the style guide itself says:

We're simply following the conventions of each language. Within JavaScript, camelCase is more natural. Within HTML, kebab-case is.

If we are gonna use kebab-case prop names, there's no reason to prevent this for component names. Otherwise the underlying logic is like: components are in JavaScript scope, props are not? Something like <WelcomeMessage greeting-text="hi"/> are clearly not natural IMO. I said PascalCase “smells like JSX” earlier, which was just another way of saying “the naming convention is not consistent”.

What I propose:

  • Always use kebab-case in templates, use PascalCase in JSX.
  • or, use PascalCase everywhere, but use camelCase for props.

@chrisvfritz
Copy link
Contributor

chrisvfritz commented Oct 1, 2017

First, thank you both for caring enough about Vue to start a discussion on this! 🙂 I'll try to explain some of the reasoning behind the decision and you can let me know what you think.

Responding to your comments

You may argue "It's Vue template, not HTML"

Yes, exactly.

then what's wrong with a single word?

I think it would be confusing to have both <Button> and <button> in the same app. Also, you would never be able to use this component in the DOM, because of the case-insensitive conflict. If you're thinking, "But obviously, we wouldn't use any existing HTML elements," then I ask: Can you predict every HTML element that might be added to the spec in the future?

PascalCases are for constructors, or classes. Instances would adopt camelCase (evil eyes here).

You're absolutely right - I mistyped here. The Vue components inside templates are not instances, but constructors. So although I used the wrong description in that comment, PascalCase is appropriate here.

Vue template is deeply coupled with HTML

Templates are actually not coupled to HTML at all. The similarities in syntax are a convenience, rather than an inevitability; people using Vue already know HTML, so we try to build on that knowledge. In fact, we'd probably be able to eliminate all the in-DOM caveats if we invented a new syntax for templates that didn't overlap with HTML and thus risk interference from the browser's parser. It would just have a steeper learning curve overall.

Properties of JavaScript objects are in camelCase

We do actually use camelCase for props in the components they're defined in. For example:

<input :value="myValueProp">

Using camelCase for props when constructing an instance of a component doesn't make sense though, because as a consumer of the component, you shouldn't have to know whether you're using a prop or an attribute. It's an implementation detail.

Other advantages of PascalCase

  • By using to kebab-case, you're fighting against the auto-completion in code editors. For example, let's say you have:

    import MyComponent from './MyComponent.vue'
    
    export default {
      components: {
        MyComponent
      },
      // ...
    }
    

    When using that component in a template, many editors will offer autocompletion for MyComponent, but never for my-component.

  • <FooBar> is more visually obvious than <foo-bar>, as there are two characters to identify it as a component: the two capital letters. In <foo-bar>, there's only one character for identification: the hyphen.

  • If you use kebab-case in an app with any non-Vue custom elements, such as a web component, then they would be indistinguishable from Vue components in templates.

What I'm currently thinking

If you both felt like this was an issue, I agree there's definitely something wrong.

I'm happy to add a more detailed explanation to the rule, and I'm also still open to updating the rule to be more permissive. At this point though, I see semantic and practical benefits to preferring PascalCase. I haven't yet seen similar benefits for kebab-case (unless I missed them, in which case I apologize).

When I read these:

"[Vue] borrows HTML a lot, and in the end will be compiled into HTML. [...] That's the reason I prefer kebab-case."
"[PascalCase is] clearly not natural IMO."

My interpretation is that you're just used to kebab-case for anything that looks like HTML, so you'd like to keep using it. Is that accurate? If it is, I think I'll need more convincing.

@Justineo
Copy link
Member

Justineo commented Oct 1, 2017

"[PascalCase is] clearly not natural IMO."

I wouldn't put it that way. What I said was:

Something like <WelcomeMessage greeting-text="hi"/> are clearly not natural IMO.

The inconsistency (within the style guide itself) make me feel unnatural, not PascalCase itself.

For props we got:

We're simply following the conventions of each language. Within JavaScript, camelCase is more natural. Within HTML, kebab-case is.

So of component tag name is a JavaScript constructor, why its prop names are suddenly HTML attributes?

<input :value="myValueProp">

In this case myValueProp is not the prop of input. It's a reference to the value of the prop value. If we have a MyInput with a prop myValue, they would both be a part of the interface of the component.

<WelcomeMessage greeting-text="hi"/>

If this is a declaration of creating a WelcomeMessage instance, the prop name is actually greetingText, not greeting-text. If the JavaScript “semantics” is to be applied here, it should be:

<WelcomeMessage greetingText="hi"/>

Enforcing kebab-case or PascalCase would both look fine to me, but it is not convincing enough to use the mixture of both in creating a single component in templates. If we are to differentiate components with elements by tag name, then why do we choose to align prop names with HTML attributes?

@chrisvfritz
Copy link
Contributor

chrisvfritz commented Oct 1, 2017

I chose to follow the local language convention for attributes because there's no benefit to doing otherwise.

  • There's no benefit to making attributes on components visually distinct from attributes outside of components. The conventions in the component name already provide the context.
  • There are no auto-completion benefits for camelCasing attribute names.
  • There's no risk of confusion with another API, unlike the ambiguity between Vue components and custom elements from another source, such as web components.

Does that make sense?

@skyline0705
Copy link

I think it not a good guide if sometime I write PascalCase but some other time I write kebab-case.

Vue.js is different from React, which one is alway can be write in html, SFC template and JSX. If it like React, only write in JSX, I think PascalCase is better. But in Vue.js I think using kebab-case can be consistent both in HTML template, SFC template and JSX

@chrisvfritz
Copy link
Contributor

@myst729 @Justineo I updated the explanation for this rule in the style guide, as I definitely agree that the reason I listed previously was insufficient. Please let me know if it still doesn't make sense to you and I'll make more changes. 🙂

If you still prefer kebab-case after this discussion, I'm also not opposed to adding a note that you can use it if you care more about consistency with HTML conventions than about the specific advantages PascalCase brings. The purpose of the style guide is not to force people to follow rules they don't find helpful, but to provide guidance and tips in cases where users might lack information or are not sure what's the best practice.

@chrisvfritz chrisvfritz changed the title Naming in PascalCase or kebal-case Naming in PascalCase or kebab-case Oct 3, 2017
@Justineo
Copy link
Member

Justineo commented Oct 5, 2017

single word for tag names & single word for props → HTML elements
kebab-case for tag names & kebab-case for props → what I think is enough
PascalCase for tag names & kebab-case for props → what current style guide think is enough
PascalCase for tag names & camelCase for props → most visually distinguishable \w consistency inside components

I think the only solid difference here is using kebab-case is not able to differentiate Vue components with Web Components. Other reasons are all subjective because there's no real line between whether the visual difference is "obvious enough". If I need two capitalized letters rather than a hyphen to increase readability, why shouldn't I increase it even more with camelCase props? Especially where there is no Web Components inside my project, I actually care more about consistency because there is visual difference already.

@chrisvfritz
Copy link
Contributor

@Justineo

why shouldn't I increase it even more with camelCase props?

Like I said, whether a component attribute is a prop or not is actually an implementation detail, so not something the consumer of the component should have to know about.

Especially where there is no Web Components inside my project

Yet. More and more JS libraries are using custom element interfaces, even when Web Components are not involved. This makes PascalCase a more future-proof pattern.

I actually care more about consistency because there is visual difference already.

As I said, I'm happy to add a note about cases like that - and I just did. 🙂 Let me know what you think and if this resolves the issue for you.

@Justineo
Copy link
Member

Justineo commented Oct 9, 2017

It seems good enough for me. Thanks for the patience and hard work!

@chrisvfritz
Copy link
Contributor

Excellent! Thanks for the discussion to help improve it. 🙂

@meteorlxy
Copy link
Member

Emmm..... But vue-router does not support PascalCase

@chrisvfritz
Copy link
Contributor

@meteorlxy Good catch and thanks for the PR to vue-router. 🙂 That'll be included in the next patch release.

@wuservices
Copy link

@chrisvfritz I came across this discussion as a Vue newbie who's trying to follow the style guide. I had just converted all my tags to PascalCase, but then I realized nothing worked anymore.

It seems like the recommendation to use PascalCase for all Vue components can be tricky, because not all components will support it. The ugly thing here is that if that's the case, you could end up in a sticky place where some of your Vue components would use PascalCase, and others would have to fall back to kebab-case, which seems confusing.

I may be misunderstanding, but it seems like you have to define the components in the correct way to make PascalCase work. If so, maybe the Vue should just automatically register PascalCase from kebab-case (or maybe there's a good reason that doesn't happen already). If my understanding is correct though, this seems non-trivial since it seems like this was even an issue for vue-router (#1162 (comment)). It looks like this used to be an issue for quasar too (quasarframework/quasar#1601), and I ran into the issue with Vuetify on the latest version.

Do plugins need to be coded in the right way for this to work?

@samburgers
Copy link

samburgers commented Dec 19, 2018

Pascal case doesn't appear to work with Async components in .jsx/.tsx files:

import Vue, { VNode } from 'vue';

export default Vue.extend({
  components: {
    HelloWorld: () => import('./HelloWorld'),
  },
  render(): VNode {
    return (
      <div>
        <HelloWorld />
      </div>
    );
  },
});

The error above is Cannot find name 'HelloWorld'.

Although all of these below appear to work fine:

import HelloWorld from './HelloWorld';

export default Vue.extend({
  render(): VNode {
    return (
      <div>
        <HelloWorld />
      </div>
    );
  },
});
export default Vue.extend({
  components: {
    helloWorld: () => import('./HelloWorld'),
  },
  render(): VNode {
    return (
      <div>
        <helloWorld />
      </div>
    );
  },
});
export default Vue.extend({
  components: {
    HelloWorld: () => import('./HelloWorld'),
  },
  render(): VNode {
    return (
      <div>
        <hello-world />
      </div>
    );
  },
});
export default Vue.extend({
  components: {
    'hello-world': () => import('./HelloWorld'),
  },
  render(): VNode {
    return (
      <div>
        <hello-world />
      </div>
    );
  },
});

Using CLI 3 for these

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

No branches or pull requests

7 participants