vue icon indicating copy to clipboard operation
vue copied to clipboard

Establish a standard way to document component and its props

Open octref opened this issue 6 years ago • 69 comments

What problem does this feature solve?

https://github.com/vuejs/vetur/issues/276

Currently, Vetur offers auto-completion & hover info for custom components defined in ElementUI, OnsenUI and Bootstrap-Vue. However, hand-maintaining such json files seem to be a lot of work. Also not co-locating the doc and the component definition can make updating component challenging.

Helper-json repos at:

  • https://github.com/ElementUI/element-helper-json
  • https://www.npmjs.com/package/vue-onsenui-helper-json
  • https://github.com/bootstrap-vue/bootstrap-vue-helper-json

This feature makes it possible to write the doc in the SFC / js component file, and auto-generate a helper json that can be used for enhancing editing experience or auto-generating API / doc website.

What does the proposed API look like?

Two more optional attributes on the default export:

export default {
  name: 'v-card',
  description: 'A card component',
  props: ['width', 'height'],
  propsDescription: [
    'width of the rendered card component',
    'height of the rendered card component'
  ]
}

I was thinking maybe using a custom block for it, but then that only applies to SFC, not js components. jsdoc might be another option. Other ideas welcome.

Another idea is similar to the typings in package.json, have a vueTypings for component libraries. It'll point to the generated helper-json file and editors could pick it up to enhance editing experience.

/cc

@Leopoldthecoder for ElementUI @masahirotanaka for OnsenUI @pi0 for Bootstrap-Vue @rstoenescu for Quasar @johnleider for Vuetify

Would you be interested in using this feature in the Vue component libraries that you are maintaining? Would you be interested in helping spec'ing a format for the generated json file and the editing experiences that should be enabled by using that file?

octref avatar Dec 05 '17 12:12 octref

Wouldn't it be more interesting to add description to the props:

props: {
     width: {
        type: Number,
         description: 'Width of the modal'
    }
}

Libs usually use the object syntax already

As long as it can be stripped off from build productions, I think it could be nice to add something like this. But maybe prepending those attributes with a special character could be even better, something like '#description' or $description to clearly make the distinction

posva avatar Dec 05 '17 12:12 posva

Does this have to be part of the component options / API? It seems they don't serve any particular purpose for Vue's own runtime behavior. In that case, would a comment-based syntax be more reasonable (as it is stripped by default)?

Using a component option would require extra toolchain changes to properly strip them.

yyx990803 avatar Dec 05 '17 14:12 yyx990803

Is it possible to read comments with the language server? 😮

posva avatar Dec 05 '17 14:12 posva

Hey. For specific BootstrapVue we needed component typings from a long time ago. The first iteration was exactly using component's runtime instance to extract props and auto-generating documentation. Here is componentdoc.vue implementation.

PROS:

  • Doesn't adds any extra bytes to runtime
  • General purpose and can be used to document any type of Vue components on the fly even at runtime.
  • Can be used for both SFCs and JS components.

CONS:

  • Can not add any custom description for each prop (If we add additional description field to props it costs lots of unneeded runtime comments)
  • Custom slots and events not supported

So we added a custom meta descriptor for components inside package.json which can be used for making a full set of docs and hintings. (Example)

Both of above methods are both used for our nuxt based docs and also for generate-vetur-helpers.js (Written by @alexsasharegan)

Maybe we can unify metadata structure which is used by BV, Onesen and Vuetify and hand-craft or auto-generate meta for any component library in either package.json or vue.json file.

This way, just like any other part of Vue's ecosystem tooling can be delegated to library authors. We may also provide some helpers making the process easier and not forcing to use specific syntax like JSDocs.

(vue-xxx/package.json or vue-xxx/vue.json)

{
  "vue": {
     "library": true,
     "components": [
         {
             "name": "b-table" // Use for hinting of <b-table> tags
             "props": [  
                 {
                   name: "title",
                   type: "Boolean",
                   description: "Table title"
                 }
             ],
          }
     ],
     directives: [
       // ....
     ]
  }
}

/CC @tmorehouse @egoist @mosinve @alexsasharegan

pi0 avatar Dec 05 '17 15:12 pi0

Something cool I've discovered so far is Project polymer's component documentation spec.

They have a nice CLI which analyzes components JSDocs and extracting meta json:

polymer analyze > analysis.json

Then using a similar component like our ComponentDoc we can use iron-component-page to visually render the docs. or exporting Via package.json for vue-language-server usage.

pi0 avatar Dec 05 '17 15:12 pi0

It would be great t have vue-language-server parse the JSDoc inside the components 😄

Akryum avatar Dec 05 '17 15:12 Akryum

@yyx990803

Does this have to be part of the component options / API? It seems they don't serve any particular purpose for Vue's own runtime behavior. In that case, would a comment-based syntax be more reasonable (as it is stripped by default)?

I disagree. I can imagine a lot of cases where Vue's warnings and devtools could be improved by this extra information. Here are some examples.

Using a component option would require extra toolchain changes to properly strip them.

Yes, but I think the changes would be quite simple, since we're already manipulating the options exported from SFCs. It's possible there's a complexity I'm not realizing though, so please correct me if that's the case.


@pi0 I'd really like to avoid having to manage a separate types file, as I find these can fall out of date very quickly when their accuracy is not enforced by a compiler. Plus, remembering to check another file every time you want to update a component is pretty significant cognitive overhead.

chrisvfritz avatar Dec 05 '17 22:12 chrisvfritz

@chrisvfritz SFCs only manipulate the options at runtime, so all the big description strings will be included in the bundle and cannot be minified. They can only be dropped with a special babel plugin or hacked buble transform.

yyx990803 avatar Dec 05 '17 22:12 yyx990803

@posva

Is it possible to read comments with the language server? 😮

It won't be easy, but it's possible, since TypeScript server supports analysis of JSDoc.

@pi0

So we added a custom meta descriptor for components inside package.json which can be used for making a full set of docs and hintings.

I agree with @chrisvfritz -- one of the strength of Vue SFC is a sensible way of organizing related information by colocating html/css/js. It'd be very un-vue-like to put this component specific information to some centralized place.

The idea is to make it possible to write components that's self-documenting, in the same spirit of JSDoc / JavaDoc / etc. This also helps reading Vue libraries' source code.

https://www.polymer-project.org/2.0/docs/tools/documentation

👍 This is something we can look into & learn from.

@yyx990803

Does this have to be part of the component options / API? It seems they don't serve any particular purpose for Vue's own runtime behavior. In that case, would a comment-based syntax be more reasonable (as it is stripped by default)?

Doesn't have to be, and I agree it doesn't help runtime and would be a bloat for code size. It has to be compiled away at build time. I think it's achievable through babel plugin though.

However, after thinking through this, I think JSDoc might be a better approach. Either way, I can't have the runtime info easily so I need to parse the script myself and find the descriptions. The parser can be reused in the vue-template-compiler 2.0 redesign: https://github.com/vuejs/vue-component-compiler/issues/28#issuecomment-333139601, where the descriptions go into descriptor.metadata or descriptor.descriptions.

Another idea is to make this a custom block in SFC like so (chose yaml since it looks cleanest)

<script>
export default {
  name: 'v-card',
  props: ['width', 'height']
}
</script>

<description>
description: A card component
props:
  width: width of the rendered card component
  height: height of the rendered card component
</description>

For libraries that's using js components, they can convert to this format by putting their js file into a <script> tag and the extra description info into this custom tag. It can be compiled away in compile time. This also makes it super easy to extract the description data.


As I mentioned this info can be used by Vetur, component doc generator, etc for better DX, but it can go beyond that. One example is ElementUI's subtags:

https://github.com/ElementUI/element-helper-json/blob/master/element-tags.json#L4

{
  "el-row": {
    "attributes": ["gutter", "type", "justify", "align", "tag"],
    "subtags": ["el-col"],
    "description": "A row in grid system"
  }
}

We can just use this info to enhance eslint-plugin-vue to warn the user, but we can do runtime check too.

octref avatar Dec 06 '17 06:12 octref

I like the idea with custom block, as this pattern already used by some VueJS-ecosystem libraries, like @kazupon vue-i18n . And we sure can easily strip this block away at compile time.

mosinve avatar Dec 06 '17 06:12 mosinve

I wrote @vuedoc/parser to generate SFC doc.

It supports:

  • Extract the component name (from the name field of from the filename)
  • Extract the component description
  • Keywords Support: You can define your own keywords with the @ symbol like @author Sébastien
  • Extract component props
  • Extract component data
  • Extract computed properties with dependencies
  • Extract component events
  • Extract component slots
  • Extract component methods

Sample:

<template>
  <label>
    <input :disabled="disabled" type="text" v-model="checkbox">
    <!-- Default slot comment -->
    <slot></slot>
    <!-- Use this slot to set the checkbox label -->
    <slot name="label">Unamed checkbox</slot>
  </label>
</template>

<script>
/**
 * A simple checkbox component
 * 
 * @author Sébastien
 */
export default {
  name: 'checkbox',
  props: {
    /**
     * The checkbox model
     * @model
     */
    value: {
      type: Array,
      required: true
    },

    /**
     * Initial checkbox value
     */
    checked: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      initialValue: null
    }
  },
  computed: {
    id () {
      return `checkbox-${this.initialValue}`
    }
  },
  created () {
    /**
     * Emit when the component has been loaded
     */
    this.$emit('loaded')
  },
  methods: {
    /**
     * Check the checbox
     */
    check () {
      /**
       * Event with identifier name
       */
      this.$emit('check', true)
    }
}
</script>

Will generate something like:

{
  "header": [
    {
      "entry": {
        "name": "checkbox" // The component name
      },
      
      // The component description
      "comments": [
        "A simple checkbox component"
      ],
      
      // Attached keywords
      keywords: [
        { name: "author", "description": "Sébastien" }
      ]
    }
  ],
  "props": [
    {
      "entry": {
        "v-model": {
          "type": "Array",
          "required": true
        }
      },
      "comments": [
        "The checbox model"
      ]
    },
    {
      "entry": {
        "checked": {
          "type": "Boolean",
          "default": true
        }
      },
      "comments": [
        "Initial checbox value"
      ]
    }
  ],
  "data": [
    {
      "visibility": "public",
      "description": null,
      "keywords": [],
      "value": null,
      "name": "initialValue"
    }
  ],
  "computed": [
    {
      "visibility": "public",
      "description": null,
      "keywords": [],
      "value": [Object],
      "name": "id",
      "dependencies": [
        "initialValue"
      ]
    }
  ],
  "slots": [
    {
      "name": "label",
      "comments": [
        "Use this slot to set the checkbox label"
      ]
    }
  ],
  "events": [
    {
      "name": "loaded",
      "comments": [
        "Emited when the component has been loaded"
      ]
    },
    {
      "name": "check",
      "comments": [
        "Event with identifier name"
      ]
    }
  ],
  "methods": [
    {
      "entry": {
        "check": {
          "type": "FunctionExpression"
        }
      },
      "comments": [
        "Check the checbox"
      ]
    }
  ]
}

demsking avatar Dec 06 '17 06:12 demsking

@yyx990803 Thanks for clarifying. 🙂 I just did a little research to see how difficult a Babel plugin would be and much to my amazement, I accidentally created a working proof-of-concept! 😄 I'm definitely not a Babel expert, so there could be issues with it, but it seemed to work on the 2 codebases I just tested it against without breaking anything. It could even be expanded to strip out other properties not needed in production, like required and validator for props.

Does this change your thoughts on feasibility at all?

chrisvfritz avatar Dec 06 '17 07:12 chrisvfritz

However, after thinking through this, I think JSDoc might be a better approach. Either way, I can't have the runtime info easily so I need to parse the script myself and find the descriptions. The parser can be reused in the vue-template-compiler 2.0 redesign: vuejs/vue-component-compiler#28 (comment), where the descriptions go into descriptor.metadata or descriptor.descriptions.

@octref Just saw this comment. When you say you can't access this information easily, how difficult are you thinking it would be and is there any way I could help explore possibilities? I ask, because I'd personally prefer to avoid JSDoc. We already have some meta information as properties (like component name and prop types) and with comments, we'd lose the ability to use additional information in the Vue's warnings or devtools.

As for the custom block, it would solve the 2nd problem assuming Vue's template compiler could parse it into a JavaScript object, but there's still the issue of component meta information being fragmented (some included as a component option, some in a new custom tag). Some other things that bother me about it:

  • If we use this information in Vue's warnings and devtools, we'll have made those dev features exclusive to SFCs, when they didn't really need to be. If we don't make this meta information exclusive to SFCs, then Vetur would have to learn how to parse it anyway for people who chose not to use the custom block.
  • From an education perspective, I'd personally prefer to avoid introducing a completely new concept in SFCs.

chrisvfritz avatar Dec 06 '17 07:12 chrisvfritz

@chrisvfritz I was thinking along the line of using TypeScript to extract the data and use it to supplement the html completion, but actually it doesn't have to be this way.

We can:

  1. Have one independent tool that takes commands such as vue-component-doc ./src/**/*.vue
  2. The tool generates a manifest file
  3. User edit vue files to add documentation
  4. manifest file is regenerated
  5. Vetur reloads the manifest file to enhance html editing

Task 1 and 2 should exist independent of Vetur, so they can also be used for other tools.

octref avatar Dec 06 '17 18:12 octref

Might want to include non SFC components and functional components (i.e. .js files)

tmorehouse avatar Dec 06 '17 18:12 tmorehouse

@octref I like the idea of tooling to create a manifest file! Then Vetur, eslint-plugin-vue, and other tooling would never have to worry about parsing that information themselves - but we still have it available for Vue's warnings and devtools. Best of all worlds. 🙂

Having a separate tool like vue-component-doc might have some issues though, since having these properties in JavaScript means they could be runtime dependent (e.g. description: myDynamicDescription()). To solve this problem, I wonder if vue-loader could build a manifest file (or perhaps a manifest file per component) at runtime? If we stored these manifest files in a standardized location, then Vetur and other tools would be able to check that location for extra information. As an added bonus, the parsing would be agnostic to syntax (e.g. Babel, TypeScript, CoffeeScript, etc). @yyx990803 Is this remotely possible? 😄 What are your thoughts?

chrisvfritz avatar Dec 06 '17 19:12 chrisvfritz

@tmorehouse It could be tricky for the Babel plugin to detect non-SFC Vue components, since files unrelated to Vue could export similarly-shaped JS objects. Also, functional components can now exist as .vue files as well, so there isn't really a reason not to use SFCs for any kind of component if you're using a build system (which you would be, if you're expecting a Babel plugin to strip out these properties in production).

chrisvfritz avatar Dec 06 '17 19:12 chrisvfritz

When generating ES style builds (transpiling to a es directory module structure) there is currently no vue-template compiler that will handle this situation. One shouldn't conclude that every one is using SFCs.

We ran into this issue when generating ES module build for Bootstrap-Vue (to allow user's to have better tree shaking), and .vue files were causing SSR errors, due to differences in vue-loader options. So we converted to using render functions instead of .vue files

tmorehouse avatar Dec 06 '17 19:12 tmorehouse

@tmorehouse According to @chrisvfritz idea we can still have both ES style builds and SFCs as the source with custom props. And stripping/extracting those comments using babel during build into meta.json.

pi0 avatar Dec 06 '17 19:12 pi0

Just as a summary to all of the nice comments until now, Something I think all of us agree on it is that we need a final manifest (Something like @demsking suggested) which can be used for both documentation and IDE hintings. The big questions are where to declare typings and how to extract it. SFCs seems the best place and we can put it inside:

  • JSDocs / TS
  • A new custom block
  • Inline props (Which will be stripped out when production compiling)

And we've got some possible methods for extracting this manifest:

  • Using vue-language-server to parse .vue files and extract the needed parts
  • Adding support for vue-loader which can extract meta when loading SFCs
  • Making an external CLI tool to do the job
  • ~Manually Document Components~

But IMO if we enforce any combination of above methods, we are still forcing a toolchain and methodology for this task. What if we just agree on a standard manifest structure and let developers make and choose the best tooling? We can then recommend and add support for the best method which is probably SFCs + vue-loader or something else ...

PS: Sorry if talking a lot. I'm just little excited about this enhancement 😆

pi0 avatar Dec 06 '17 19:12 pi0

I want to add some observations from Vetur's report.

Most users use third library as global plugin. E.g. element-ui/bootstrap-vue provide global components. It would be hard for Vue-language-server to find all components in a third lib if we don't specify library component manifest. So declaration extraction is also crucial in the spec.

Another problem is how to ship component library. We should support shipping component in SFC format as well as compiled JS.

@pi0 has already done an awesome summary. Thanks!

HerringtonDarkholme avatar Dec 07 '17 09:12 HerringtonDarkholme

Another problem is how to ship component library. We should support shipping component in SFC format as well as compiled JS.

SFC should be preferred over JS as component render functions are built in two ways now – a javascript function (in browser build) and string concatenation (in SSR build).


I'm experimenting with a utility library to define prop validation rules. I'm shimming all utility functions for the production build, then using prepack, extra meta is dropped. I think similar techniques can be used for documentation. Maybe a babel plugin can analyze AST and export meta at compile time.

znck avatar Dec 08 '17 21:12 znck

I agree with Evan this does not seem to serve any particular purpose. If you want to allow this feature however I like @pi0 idea of adding it inside the prop

samayo avatar Dec 14 '17 09:12 samayo

Some time ago, I was interested in creating a Vetur integration with Quasar framework by @rstoenescu (which I delayed as it approaches 0.15 and 1.0). As Quasar is deliberately huge, I toyed a very simple JS-to-Vetur-like JSON converter to make things more sane.

From this (brief) experience, my two cents is that, if the community is going to settle on the custom block approach, JS (e.g. export default some object + some import custom logic) seems like a better alternative to structured-text formats (like YAML or JSDoc), due to some use cases where it'll avoid duplication, thus improving maintainability, like the following:

  • Components extending other components;
  • Components sharing common properties.

Disconsider this message if this is doable otherwise and I'm missing something.

leopiccionia avatar Jan 08 '18 02:01 leopiccionia

One can also use propdocs which presents a pretty neat (although maybe not original) way to write documentation for Vue components. You can add descriptions, notes (even explanatory code), flags, etc. as props and even render this documentation as a separate component.

geni94 avatar May 04 '18 15:05 geni94

I made a proposal: https://github.com/octref/vuetypes. Feedback welcome!

My proposal is only about the "JSON format" and the "Editor Behavior" part. I don't know how each library is building the current helper json files for Vetur, so I hope I can get some feedback from @alexsasharegan @rstoenescu @asial-matagawa @jtommy @QingWei-Li @nekosaur.

Also, would any of you be interested in collaborating with me to build the JSON of this new format for your library? We can add documentation to your library's source code & work on the tool to extract the metadata into a JSON. I can do most of the work, but I probably would need help from the library authors.

There are still open questions as to where & how to author the data. @leopiccionia has a point: If 10 components all have the width attribute, it feels messy to have 10 descriptions scattered in 10 Vue files.

octref avatar Sep 06 '18 23:09 octref

I see this thread mentioned for continuing the proposal discussion. Is this where we want to comment, or should we encourage issues to be raised on the proposal repo?

alexsasharegan avatar Sep 07 '18 00:09 alexsasharegan

There are 3 things:

  1. How / Where to write the typing data in source code
  2. How to store & distribute the data
  3. What the data should look like & what editor should do based on the data

For 1, continue the discussion here. For 2/3, open issues at https://github.com/octref/vuetypes,

octref avatar Sep 07 '18 03:09 octref

Regarding How / Where to write the typing data, I'm in favor of the first way the proposal illustrates where to put typing data:

Put it in the NPM module you are publishing, and add a key vueTypes that points to the path of the file. Benefit is the JSON always has the right version.

In effect, this just following the existing behavior of things like main, module, and types. Familiarity is good, and I haven't seen any side-effects to this approach.

A package.json approach also means library authors can use build-time scripting to generate an up-to-date set of component definitions. There are no permissions to manage for publishing apart from what is required for publishing a library in general.

The second option listed is:

Publish it a VerilyTyped, which can automatically publish it to vuetypes. (I don't know if this is worth the hassle)

The only benefit to a separate repository (not mutually exclusive to a package.json distribution) is that it allows community members to produce component defs for libraries that don't have them available. This is definitely valuable, but far less pressing.

The relationship between the two distribution methods is exactly what we see already in the typescript community with embedded library definitions and community-contributed definitions to DefinitelyTyped. I think this is a good balance, but embedded publishing is the primary concern and a community registry a secondary one.

alexsasharegan avatar Sep 07 '18 14:09 alexsasharegan

@alexsasharegan That is about topic 2. I should have made it more explicit — topic 1 is how / where to write the data in your source code (JSDocs, fields of default export, custom block, etc).

octref avatar Sep 07 '18 17:09 octref