Vue Component Workbench

Vue Component Workbench
Photo by Barn Images / Unsplash

In my projects I've been using my vue/scss framework "hop". Whenever I add new components or change something, it is quite helpful to have an instance of webpack running inside the hop project to see my changes in realtime instead of publishing a new version and updating the depending project.

Currently, this is just a folder called "test" with a vue instance to play around with. I just commit whatever state it has once I am done with testing.

There is also no documentation because "Code is documentation enough".

I cannot keep every functionality in mind so whenever I'm in a situation like "wait I used a dropdown menu before somewhere else, how does that work again?", I start searching in other projects and copy paste it. (Which is one of the reasons I built my app gitseeker)

So I sat down and researched a bit about how to document my components. The first result is an "official" tool called vue styleguidist, it automatically scans the jsdoc information, it allows to use vue in markdown and ... it uses react under the hood...? Wait what?

Apparently it is just a slightly modified version of react styleguidist. This becomes even more clear when running npm install where you are welcomed with a 86 line long paragraph of react peer dependency warnings:

I works nevertheless but it seems a bit fishy and if I ever want to modify the template it needs to be done in react.

Other component workbench tools didn't appear good to me either, Vue suggests you to use vuepress... but I already had enough fun with that in my other blogpost

https://vue-community.org/guide/ecosystem/documentation.html#documentation-platforms

So quite frustrated I fired up a basic webpack config and did what I did way too many times: reinventing the wheel using node and vue.

Examples

The first challenge was finding a way to have codesandbox-like examples so you can see and interact with the components but also see the source code of the example without me manual copy pasting the sourcecode around.

First version

Getting the content of a file is possible using webpack raw-loader which is deprecated since v5 in preference of asset modules. Loading the component is possible by simply importing it, but that would mean two imports for every example (one for the component, one for its sourcecode). So I wrote a component that loads both using webpacks dynamic imports:

The webpack rules

module: {
    rules: [
        {
            resourceQuery: /raw/,
            type: 'asset/source'
        },
        {
            test: /\.vue$/,
            resourceQuery: { not: [/raw/] },
            loader: 'vue-loader'
        },
    ]
}

The component

<template>
    <div v-if="component">
        <h3 class="d-flex flex-center"><h-icon icon="flask" left /> Example</h3>
        <section>
            <component :is="component" />
            <div class="mt-3">
                <div :class="['btn flat', show_code ? 'active' : '']" @click="show_code = !show_code"><h-icon icon="code" left />{{ show_code ? "Hide" : "Show" }} Code</div>
            </div>
            <highlightjs class="mt-3" language="vue" :code="code" v-show="show_code" />
        </section>
    </div>
</template>

<script>
import { ref, shallowRef } from "vue";

export default {
    props: ["of"],
    setup(props) {
        const show_code = ref(false);
        const component = shallowRef(null);
        const code = ref("");
        const highlighted = ref("");

        import("../components/" + props.of + ".vue")
            .then((res) => {
                component.value = res.default;

                import("../components/" + props.of + ".vue?raw").then((res) => {
                    code.value = res.default;
                });
            })
            .catch((e) => {
                //has no example
                console.log(props.of + " has no example");
            });

        return {
            show_code,
            component,
            code,
            highlighted,
        };
    },
};
</script>

Markdown

Next up was markdown for the description texts. Since im trying to keep it as straight forward as possible I added a component for that too

<script>
import { h } from "vue";
import markdownIt from "markdown-it";

export default {
    setup(props, { slots, attrs }) {
        let md = new markdownIt({
            breaks: true,
        });

        function renderElement(e) {
            if (typeof e.children == "string") {
                return h(typeof e.type === "symbol" ? "span" : e.type, { innerHTML: md.renderInline(e.children) });
            } else {
                return h(e.type, e.children.map(renderElement));
            }
        }

        return () => {
            return [h("p", attrs, slots.default().map(renderElement))];
        };
    },
};
</script>

It uses a vue render function that gets the contents of the default slot and renders it using markdown-it

This does mean I don't get markdown syntax highlighting in vscode, but a) it is faster to write than creating .md files for every paragraph and b) markdown syntax highlighting so not super important or helpful in my opinion

Component Info

vue styleguidist shows the jsdoc information parsed from the comments which is very helpful both for reading the docs and writing the docs.

Under the hood it uses vue-docgen-api which does all the parsing for me. It cannot run inside the browser so I created a webpack plugin that hooks into the compilation:

  • loop over all vue files in a specified "source" folder
  • get the component info as js object, save it to a big object
  • save the object as json file in the webpack output folder

This way I can fetch the json file from the frontend and access the component infos to render tables:

A good documentation needs a search. I did not want to overdo it here so I simply re-used the component info json to search in all description texts and prop names

This can be further expanded by adding the contents of all markdown components to the search index for example

Sections and Menu

Every component I want to document is added as a doc section which combines a few features:

  • push the component to the menu array using vue provide/inject
  • generate an id using lodash.kebabcase for a navigation anchor
  • render the component info
  • render an example if a file "Example"+component_name+".vue" exists
  • a slot to add additional information

So the only thing I have to do to document a component is add it in the App.vue, throw some comments in the component and optionally create an Example vue component once I want to showcase or test a components features