Related dependent versions:

  • The node v10.15.0
  • NPM v6.4.1
  • Yarn v1.22.10
  • Vue - cli v4.5.9
  • @ vue/compiler v3.0.4

Making: vue – source – demo

1. Preface (Demand)

You want to read the *. Vue file source code and highlight it on the page without using third party dependencies.

2. Implementation idea

Through vue-Loader custom block function, obtain the file path of the target file, and then read the source code through FS, and then use API baseParse of @vue/ Compiler-core to convert the read content into AST syntax abstract tree. Then remove the custom block content and the required source code from the content read by FS. Finally, re-attach the above two contents to the component object and directly read the corresponding fields of the component.

Perfect. Turn it off. Knock it off.

3. The implementation

Now that the idea is very clear, it’s time to implement it.

3.1 Project initialization

Vue – CLI to create a quick template build project, here using vue version 2, and then using vite + vue3 to implement another.

The project runs like this, and I don’t want to go into details here.

3.2 Custom Blocks

Here refer to the vue-loader official website example, very simple. If you don’t know, you can go to the official website.

  1. createloaderfileplugins/docs-loader.js
module.exports = function (source, map) {
    this.callback(
        null.`export default function (Component) {
            Component.options.__docs = The ${JSON.stringify(source)
            }} `,
        map
    )
}
Copy the code
  1. createvue.config.jsThe configuration rules use those defined aboveloader
const docsLoader = require.resolve('./plugins/docs-loader.js')

module.exports = {
    configureWebpack: {
        module: {
            rules: [{resourceQuery: /blockType=docs/,
                    loader: docsLoader
                }
            ]
        }
    }
}

Copy the code

Note: modified configuration related files need to run the project again

  1. use

src/components/demo.vue

<docs>I am ComponentB Docs custom fast content</docs>

<template>
    <div>ComponentB components</div>
</template>

<script>
    export default {
        name: "ComponentB"
    }
</script>

<style scoped>

</style>
Copy the code

src/App.vue

<template>
    <div id="app">
        <demo/>
        <p>{{demoDocs}}</p>
    </div>
</template>

<script>
    import Demo from './components/demo'

    export default {
        name: 'App'.components: {
            Demo
        },
        data () {
            return {
                demoDocs: Demo.__docs
            }
        }
    }
</script>

Copy the code

Effect:

Putting the Demo component on the console is a bit more obvious:

3.4 Obtaining the File path and displaying the File Contents

It took a long time to get the path to the file, and it was mentioned on webpack’s English website. So I went to print loader’s this, really have everything, if I had known earlier, I would have printed it. Left no technical tears.

Now that we have the full path to the target file, let’s get started! To add a little more detail to our custom loader:

Need to install some dependencies before doing:

yarn add -D @vue/compiler-core
Copy the code
const fs = require('fs');
const {baseParse} = require('@vue/compiler-core');

module.exports = function (source, map) {
    // 1. Get the full path of the file with the 
       tag
    const {resourcePath} = this
    // 2. Read the file contents
    const file = fs.readFileSync(resourcePath).toString()
    // 3. Use baseParse to convert string templates into AST abstract syntax trees
    const parsed = baseParse(file).children.find(n= > n.tag === 'docs')
    4 / / title
    const title = parsed.children[0].content
    // 5. Separate the 
       tags from the content
    const main = file.split(parsed.loc.source).join(' ').trim()
    // 6. Go back and add to the component object
    this.callback(
        null.`export default function (Component) {
          Component.options.__sourceCode = The ${JSON.stringify(main)}
          Component.options.__sourceCodeTitle = The ${JSON.stringify(title)}} `,
        map
    )
}

Copy the code

Complete the above steps and remember to rerun the project. Now let’s see how it works:

em… Good. We have all the Demo components we need. Use the Pre tag to display it:

<template>
    <div id="app">
        <demo/>
        <p>{{sourceCodeTitle}}</p>
        <pre v-text="sourceCode"></pre>
    </div>
</template>

<script>
    import Demo from './components/demo'

    export default {
        name: 'App'.components: {
            Demo
        },
        data () {
            return {
                sourceCodeTitle: Demo.__sourceCodeTitle,
                sourceCode: Demo.__sourceCode
            }
        },
        mounted() {
            console.log('Demo', Demo)
        }
    }
</script>

Copy the code

It seems that all the requirements have been realized here, which is very easy. How can I stop here as a breadwinner who has just graduated for five months? I decided to highlight the plain code and make it look nice.

3.5 Code highlighting

Code highlighting uses a highlightJS with a high star.

Installation:

yarn add highlight.js
Copy the code

Use:

src/App.vue

<template>
    <div id="app">
        <demo/>
        <p>{{sourceCodeTitle}}</p>
        <pre>
            <code class="language-html" ref="code" v-text="sourceCode" />
        </pre>
    </div>
</template>

<script>
    import Demo from './components/demo'
    import highlightjs from 'highlight.js'
    import 'highlight.js/styles/vs2015.css'

    export default {
        name: 'App'.components: {
            Demo
        },
        data () {
            return {
                sourceCodeTitle: Demo.__sourceCodeTitle,
                sourceCode: Demo.__sourceCode
            }
        },
        async mounted() {
            await this.$nextTick()
            this.init()
        },
        methods: {
            init () {
                const codeEl = this.$refs.code
                highlightjs.highlightBlock(codeEl)
            }
        }
    }
</script>

Copy the code

Effect:

The code is highlighted in your favorite color. Bright is bright up, but write is one-time code, not in line with the requirements of dry rice people, is it possible to encapsulate a public component specifically to see the effect of the component and the source code!

3.6 Component Encapsulation

Before encapsulating a component, what should it look like? With a question in mind, I browsed the documentation pages of various excellent wheels and drew the following design:

Start global component encapsulation:

  1. src/components/component-source-demo/src/index.vue

    <template>
        <div class="component-source-demo">
            <h2 class="component-source-demo__title">{{title || component.__sourceCodeTitle}}</h2>
            <div class="component-source-demo__description">{{description}}</div>
            <div class="component-source-demo__component">
                <component :is="component" :key="component.__sourceCodeTitle"/>
            </div>
            <div class="component-source-demo__action">
                <button type="button" @click="handleCodeVisible('hide')" v-if="codeVisible">Hidden code ↑</button>
                <button type="button" @click="handleCodeVisible('show')" v-else>View code ↓</button>
            </div>
            <div class="component-source-demo__code" v-show="codeVisible">
          <pre>
            <code class="html" ref="code" v-text="component.__sourceCode"/>
          </pre>
            </div>
        </div>
    </template>
    
    <script>
        import {highlightBlock} from 'highlight.js';
        import 'highlight.js/styles/vs2015.css'
    
        export default {
            name: "component-source-demo".props: {
                title: String.description: String.component: {
                    type: Object.required: true}},data() {
                return {
                    codeVisible: true}},async mounted() {
                await this.$nextTick()
                this.init()
            },
            methods: {
                init () {
                    const codeEl = this.$refs.code
                    highlightBlock(codeEl)
                },
                handleCodeVisible(status) {
                    this.codeVisible = status === 'show'}}}</script>
    
    <style scoped>
    
    </style>
    
    Copy the code
  2. src/components/component-source-demo/index.js

    import ComponentSourceDemo from './src/index'
    
    ComponentSourceDemo.install = (Vue) = > Vue.component(ComponentSourceDemo.name, ComponentSourceDemo)
    
    export default ComponentSourceDemo
    
    Copy the code

Use:

  1. SRC /mian.js Globally registers the component

  2. src/App.vue

    <template>
        <div id="app">
            <component-source-demo :component="Demo"/>
        </div>
    </template>
    
    <script>
        import Demo from './components/demo'
    
        export default {
            name: 'App',
            data () {
                return {
                    Demo
                }
            }
        }
    </script>
    
    Copy the code

    Code is very clean, comfortable!! The effect is also very good, party A is very satisfied.

    It’s still a bit of a fly in the ointment, if you have a lot of components to show. Wouldn’t that be a lot of repetitive code, and being a good cook doesn’t allow that to happen, so the code needs to be optimized.

3.7 Code Optimization

3.7.1 Automatic Component Import

src/App.vue

<template>
    <div id="app">
        <component-source-demo
                v-for="item in componentList"
                :key="item.name"
                :component="item"
        />
    </div>
</template>

<script>
    export default {
        name: 'App',
        data () {
            return {
                componentList: []}},mounted() {
            this.autoImportComponents()
        },
        methods: {
            autoImportComponents () {
                const moduleList = require.context('./components/demo'.false./\.vue$/)
                const requireAll = requireContext= > requireContext.keys().map(requireContext)
                let targetModuleList = requireAll(moduleList)
                this.componentList = targetModuleList.map(module= > {
                    return module.default
                })
            }
        }
    }
</script>

Copy the code

Now we just need to add new components to components/demo, we just need to refresh the Webpack to automatically read the components for us.

4. To summarize

Here basically completed, a lot of knowledge points are now learn now sell, if where to say wrong hope we point out, where to say bad hope we forgive.

Here we need to thank Teacher Fang Yinghang fangfang for providing ideas.