4
头图

foreword

In "An article that takes you to build a blog with VuePress + Github Pages" , we used VuePress to build a blog, check the final effect: TypeScript Chinese document .

But in the process of building a VuePress blog, not all plug-ins can meet the needs, so in this article, we take implementing a code copy plug-in as an example to teach you how to implement a VuePress plug-in from scratch.

local development

The first problem to be solved in developing plugins is how to develop locally. We looked at the " Development Plugin " chapter of the VuePress 1.0 official document and found no solution, but in the " local plugin " of the VuePress 2.0 official document, It has written:

It is recommended that you use the configuration file directly as a plugin, because almost all plugin APIs can be used in the configuration file, which is more convenient in most scenarios.

But if you have too many things to do in the config file, it's better to extract them into separate plugins and use them either by setting absolute paths or by requiring:

module.exports = {
  plugins: [
    path.resolve(__dirname, './path/to/your-plugin.js'),
    require('./another-plugin'),
  ],
}

So let's get started!

Initialize the project​

We create a new folder .vuepress vuepress-plugin-code-copy folder to store the code related to the plug-in, and then enter the folder with the command line, execute npm init , and create package.json . At this time, the directory of the file is:

.vuepress
├─ vuepress-plugin-code-copy 
│  └─ package.json
└─ config.js        

We vuepress-plugin-code-copy new next index.js file reference official document plug-in example in writing, we use function returns an object form, this function accepts plug-in configuration options as the first argument, ctx contains the context of the object as the first compile Two parameters:

module.exports = (options, ctx) => {
   return {
      // ...
   }
}

name in the official document Option API ready hook in the life cycle function, we write an initial test code:

module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        async ready() {
            console.log('Hello World!');
        }
    }
 }

At this point, we run yarn run docs:dev , and we can see our plugin name and print results during the running process:

Plugin Design

Now we can imagine the effect of our code duplication plugin, the effect I want to achieve is:​

There is a Copy text button in the lower right corner of the code block. After clicking, the text becomes Copyed! Then after a second, the text becomes Copy again, and the code in the code block is copied to the clipboard when clicked. The expected performance is as follows:

copy.gif

Plugin development

If it is in the Vue component, we can easily achieve this effect. When the root component is mounted or updated , use document.querySelector get all the code blocks, insert a button element, and then bind the click event to the button element. When the click event is triggered When the code is copied to the clipboard, then modify the text, and then modify the text after 1s.

So is there a way for the VuePress plugin to control the lifecycle of the root component? Option API of the VuePress official documentation, and we can find that VuePress provides a clientRootMixin method:

Path to a mixin file that allows you to control the lifecycle of the root component

Take a look at the sample code:

// 插件的入口
const path = require('path')

module.exports = {
  clientRootMixin: path.resolve(__dirname, 'mixin.js')
}
// mixin.js
export default {
  created () {},
  mounted () {}
}

Isn't that what we need? Then let's do it, modify the content of index.js

const path = require('path');

module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
    }
 }

In vuepress-plugin-code-copy a new lower clientRootMixin.js file, the code is written:

export default {
    updated() {
        setTimeout(() => {
            document.querySelectorAll('div[class*="language-"] pre').forEach(el => {
                                console.log('one code block')
            })
        }, 100)
    }
}

Refresh the page in your browser and see the print:


The next step is to think about how to write the button element.​

Of course, we can use native JavaScript to create elements a little bit, and then insert them, but we are actually in a project that supports Vue syntax, in fact, we can create a Vue component, and then mount an instance of the component to the element. So how do you mount it?

We can find the 061e939b251bf4 API in Vue's global API Vue.extend look at the usage example:

// 要挂载的元素
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')

The result is as follows:

// 结果为:
<p>Walter White aka Heisenberg</p>

Next, we create a Vue component and mount it to each code block element Vue.extend

In vuepress-plugin-code-copy a new lower CodeCopy.vue file, code is written as follows:

<template>
    <span class="code-copy-btn" @click="copyToClipboard">{{ buttonText }}</span>
</template>

<script>
export default {
    data() {
        return {
            buttonText: 'Copy'
        }
    },
    methods: {
        copyToClipboard(el) {
            this.setClipboard(this.code, this.setText);
        },
        setClipboard(code, cb) {
            if (navigator.clipboard) {
                navigator.clipboard.writeText(code).then(
                    cb,
                    () => {}
                )
            } else {
                let copyelement = document.createElement('textarea')
                document.body.appendChild(copyelement)
                copyelement.value = code
                copyelement.select()
                document.execCommand('Copy')
                copyelement.remove()
                cb()
            }
        },
        setText() {
            this.buttonText = 'Copied!'

            setTimeout(() => {
                this.buttonText = 'Copy'
            }, 1000)
        }
    }
}
</script>

<style scoped>
.code-copy-btn {
    position: absolute;
    bottom: 10px;
    right: 7.5px;
    opacity: 0.75;
    cursor: pointer;
    font-size: 14px;
}

.code-copy-btn:hover {
    opacity: 1;
}
</style>

This component implements the style of the button and the effect of writing the code into the cut version when clicked. The overall code is relatively simple and will not be described further.

Let's modify clientRootMixin.js :

import CodeCopy from './CodeCopy.vue'
import Vue from 'vue'

export default {
    updated() {
        // 防止阻塞
        setTimeout(() => {
            document.querySelectorAll('div[class*="language-"] pre').forEach(el => {
                  // 防止重复写入
                if (el.classList.contains('code-copy-added')) return
                let ComponentClass = Vue.extend(CodeCopy)
                let instance = new ComponentClass()
                instance.code = el.innerText
                instance.$mount()
                el.classList.add('code-copy-added')
                el.appendChild(instance.$el)
            })
        }, 100)
    }
}

Note two points here, the first is that we get the content of the code to be copied el.innerText code property of the instance. In the component, we get it this.code

The second is that we did not use $mount(element) , and directly passed in a node element to be mounted. This is because $mount() will clear the target element, but here we need to add it to the element, so after executing instance.$mount() , instance.$el obtain it through 061e939b251e7c Instance element, and then appendChild into each code block. $el the use of 061e939b251e80, please refer to the el chapter official document.

At this point, our file directory is as follows:

.vuepress
├─ vuepress-plugin-code-copy 
│  ├─ CodeCopy.vue
│  ├─ clientRootMixin.js
│  ├─ index.js
│  └─ package.json
└─ config.js   

So far, in fact, we have implemented the function of code copying.

Plugin options

Sometimes, in order to increase the scalability of the plug-in, optional configuration options are allowed. For example, we don't want the text of the button to be Copy, but "Copy" in Chinese. After copying, the text becomes "Copyed!", How to achieve this?

As mentioned earlier, the first parameter of the function exported by index.js

const path = require('path');

module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
    }
 }

We first write the options config.js

module.exports = {
    plugins: [
      [
        require('./vuepress-plugin-code-copy'),
        {
          'copybuttonText': '复制',
          'copiedButtonText': '已复制!'
        }
      ]
    ]
}

We can receive index.js in 061e939b251fe3 through the options config.js , but how do we pass these parameters into the CodeCopy.vue file?

Let's turn to the Option API provided by VuePress, and we can find that there is a define API . In fact, this define attribute is to define the global variables used inside our plugin. We modify index.js :

const path = require('path');

module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        define: {
            copybuttonText: options.copybuttonText || 'copy',
            copiedButtonText: options.copiedButtonText || "copied!"
        },
        clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
    }
 }

Now that we have written two global variables, how to use them in the component? The answer is to use it directly!

We modify the code CodeCopy.vue

// ...
<script>
export default {
    data() {
        return {
            buttonText: copybuttonText
        }
    },
    methods: {
        copyToClipboard(el) {
            this.setClipboard(this.code, this.setText);
        },
        setClipboard(code, cb) {
            if (navigator.clipboard) {
                navigator.clipboard.writeText(code).then(
                    cb,
                    () => {}
                )
            } else {
                let copyelement = document.createElement('textarea')
                document.body.appendChild(copyelement)
                copyelement.value = code
                copyelement.select()
                document.execCommand('Copy')
                copyelement.remove()
                cb()
            }
        },
        setText() {
            this.buttonText = copiedButtonText

            setTimeout(() => {
                this.buttonText = copybuttonText
            }, 1000)
        }
    }
}
</script>
// ...

The final effect is as follows:

1234.gif

code reference

Complete code view: https://github.com/mqyqingfeng/Blog/tree/master/demos/VuePress/vuepress-plugin-code-copy

In fact Benpian code reference Vuepress Code Copy Plugin the plug-in code, click View source address .

series of articles

The blog building series is the only series of practical tutorials I have written so far, explaining how to use VuePress to build a blog and deploy it to GitHub, Gitee, personal servers and other platforms.

  1. An article that takes you to build a blog with VuePress + GitHub Pages
  2. An article teaches you how to synchronize GitHub and Gitee code
  3. Not using GitHub Actions yet? Check out this
  4. How does Gitee automatically deploy Pages? Or use GitHub Actions!
  5. A front-end Linux command
  6. A simple and sufficient Nginx Location configuration explanation
  7. A detailed tutorial from purchasing a server to deploying blog code
  8. A detailed tutorial of domain name from purchase to filing to resolution
  9. How to set the last updated time of VuePress blog optimization
  10. VuePress blog optimization of the added data statistics
  11. VuePress blog optimization to open HTTPS
  12. VuePress blog optimization to open Gzip compression

WeChat: "mqyqingfeng", add me to the only readership of Xianyu.

If there are any mistakes or inaccuracies, please be sure to correct me, thank you very much. If you like or have inspiration, welcome to star, which is also an encouragement to the author.


冴羽
9.3k 声望6.3k 粉丝

17 年开始写前端文章,至今 6 个系列,上百篇文章,全网千万阅读