从零实现一个 VuePress 插件

简介: 在搭建 VuePress 博客的过程中,也并不是所有的插件都能满足需求,所以本篇我们以实现一个代码复制插件为例,教大家如何从零实现一个 VuePress 插件。

0.png


前言


《一篇带你用 VuePress + Github Pages 搭建博客》中,我们使用 VuePress 搭建了一个博客,最终的效果查看:TypeScript 中文文档


但在搭建 VuePress 博客的过程中,也并不是所有的插件都能满足需求,所以本篇我们以实现一个代码复制插件为例,教大家如何从零实现一个 VuePress 插件。


本地开发


开发插件第一个要解决的问题就是如何本地开发,我们查看 VuePress 1.0 官方文档的「开发插件」章节,并没有找到解决方案,但在 VuePress 2.0 官方文档的「本地插件」里,却有写道:


推荐你直接将 配置文件 作为插件使用,因为几乎所有的插件 API 都可以在配置文件中使用,这在绝大多数场景下都更为方便。

但是如果你在配置文件中要做的事情太多了,最好还是将它们提取到单独的插件中,然后通过设置绝对路径或者通过 require 来使用它们:


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


那就让我们开始吧!


初始化项目


我们在 .vuepress 文件夹下新建一个 vuepress-plugin-code-copy 的文件夹,用于存放插件相关的代码,然后命令行进入到该文件夹,执行 npm init,创建 package.json,此时文件的目录为:


.vuepress
├─ vuepress-plugin-code-copy 
│  └─ package.json
└─ config.js        
复制代码


我们在 vuepress-plugin-code-copy下新建一个 index.js 文件,参照官方文档插件示例中的写法,我们使用返回对象的函数形式,这个函数接受插件的配置选项作为第一个参数、包含编译期上下文的 ctx 对象作为第二个参数:


module.exports = (options, ctx) => {
   return {
      // ...
   }
}
复制代码


再参照官方文档 Option API 中的 name,以及生命周期函数中的 ready 钩子,我们写一个初始的测试代码:


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


此时我们运行下 yarn run docs:dev,可以在运行过程中看到我们的插件名字和打印结果:


6.png


插件设计


现在我们可以设想下我们的代码复制插件的效果了,我想要实现的效果是:


在代码块的右下角有一个 Copy 文字按钮,点击后文字变为 Copied!然后一秒后文字重新变为 Copy,而代码块里的代码则在点击的时候复制到剪切板中,期望的表现效果如下:


7.png


插件开发


如果是在 Vue 组件中,我们很容易实现这个效果,在根组件 mounted 或者 updated的时候,使用 document.querySelector获取所有的代码块,插入一个按钮元素,再在按钮元素上绑定点击事件,当触发点击事件的时候,代码复制到剪切板,然后修改文字,1s 后再修改下文字。


那 VuePress 插件有方法可以控制根组件的生命周期吗?我们查阅下 VuePress 官方文档的 Option API,可以发现 VuePress 提供了一个 clientRootMixin 方法:


指向 mixin 文件的路径,它让你可以控制根组件的生命周期


看下示例代码:


// 插件的入口
const path = require('path')
module.exports = {
  clientRootMixin: path.resolve(__dirname, 'mixin.js')
}
复制代码


// mixin.js
export default {
  created () {},
  mounted () {}
}
复制代码


这不就是我们需要的吗?那我们动手吧,修改 index.js的内容为:


const path = require('path');
module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
    }
 }
复制代码


vuepress-plugin-code-copy下新建一个 clientRootMixin.js文件,代码写入:


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


刷新下浏览器里的页面,然后查看打印:


8.png


接下来就要思考如何写入按钮元素了。


当然我们可以使用原生 JavaScript 一点点的创建元素,然后插入其中,但我们其实是在一个支持 Vue 语法的项目里,其实我们完全可以创建一个 Vue 组件,然后将组件的实例挂载到元素上。那用什么方法挂载呢?


我们可以在 Vue 的全局 API 里,找到 Vue.extendAPI,看一下使用示例:


// 要挂载的元素
<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')
复制代码


结果如下:


// 结果为:
<p>Walter White aka Heisenberg</p>
复制代码


那接下来,我们就创建一个 Vue 组件,然后通过 Vue.extend 方法,挂载到每个代码块元素中。


vuepress-plugin-code-copy下新建一个 CodeCopy.vue 文件,写入代码如下:


<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>
复制代码


该组件实现了按钮的样式和点击时将代码写入剪切版的效果,整体代码比较简单,就不多叙述了。


我们修改一下 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)
    }
}
复制代码


这里注意两点,第一是我们通过 el.innerText 获取要复制的代码内容,然后写入到实例的 code 属性,在组件中,我们是通过 this.code获取的。


第二是我们没有使用 $mount(element),直接传入一个要挂载的节点元素,这是因为 $mount() 的挂载会清空目标元素,但是这里我们需要添加到元素中,所以我们在执行 instance.$mount()后,通过 instance.$el获取了实例元素,然后再将其 appendChild 到每个代码块中。关于 $el的使用可以参考官方文档的 el 章节


此时,我们的文件目录如下:


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


至此,其实我们就已经实现了代码复制的功能。


插件选项


有的时候,为了增加插件的可拓展性,会允许配置可选项,就比如我们不希望按钮的文字是 Copy,而是中文的「复制」,复制完后,文字变为 「已复制!」,该如何实现呢?

前面讲到,我们的 index.js导出的函数,第一个参数就是 options 参数:


const path = require('path');
module.exports = (options, ctx) => {
    return {
        name: 'vuepress-plugin-code-copy',
        clientRootMixin: path.resolve(__dirname, 'clientRootMixin.js')
    }
 }
复制代码


我们在 config.js先写入需要用到的选项:


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


我们 index.js中通过 options参数可以接收到我们在 config.js 写入的选项,但我们怎么把这些参数传入 CodeCopy.vue 文件呢?


我们再翻下 VuePress 提供的 Option API,可以发现有一个 define API,其实这个 define 属性就是定义我们插件内部使用的全局变量。我们修改下 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')
    }
 }
复制代码


现在我们已经写入了两个全局变量,组件里怎么使用呢?答案是直接使用!


我们修改下 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>
// ...
复制代码


最终的效果如下:


9.png


代码参考


完整的代码查看:github.com/mqyqingfeng…


其实本篇代码是参考了 Vuepress Code Copy Plugin这个插件的代码,点击查看源码地址


系列文章


博客搭建系列是我至今写的唯一一个偏实战的系列教程,讲解如何使用 VuePress 搭建博客,并部署到 GitHub、Gitee、个人服务器等平台。


  1. 一篇带你用 VuePress + GitHub Pages 搭建博客


  1. 一篇教你代码同步 GitHub 和 Gitee


  1. 还不会用 GitHub Actions ?看看这篇


  1. Gitee 如何自动部署 Pages?还是用 GitHub Actions!


  1. 一份前端够用的 Linux 命令


  1. 一份简单够用的 Nginx Location 配置讲解


  1. 一篇从购买服务器到部署博客代码的详细教程


  1. 一篇域名从购买到备案到解析的详细教程


  1. VuePress 博客优化之 last updated 最后更新时间如何设置


  1. VuePress 博客优化之添加数据统计功能


  1. VuePress 博客优化之开启 HTTPS


  1. VuePress 博客优化之开启 Gzip 压缩


微信:「mqyqingfeng」,加我进冴羽唯一的读者群。


如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者 有所启发,欢迎 star,对作者也是一种鼓励。



目录
相关文章
|
3月前
|
安全 Java 编译器
什么是AOP面向切面编程?怎么简单理解?
本文介绍了面向切面编程(AOP)的基本概念和原理,解释了如何通过分离横切关注点(如日志、事务管理等)来增强代码的模块化和可维护性。AOP的核心概念包括切面、连接点、切入点、通知和织入。文章还提供了一个使用Spring AOP的简单示例,展示了如何定义和应用切面。
493 1
什么是AOP面向切面编程?怎么简单理解?
|
JavaScript
vue版本冲突解决办法
ERROR in Vue packages version mismatch: vue@2.1.4 vue-template-compiler@2.1.5 This may cause things to work incorrectly.
5686 0
|
移动开发 JavaScript 前端开发
Element Plus for Vue 3 入门教程
Element Plus 是为了适配 Vue 3 对 Element UI 进行的重构。Vue 3.0 的这次大版本升级,对于第三方组件库来说是一件好事,那些已经修修补补无数次,还无法彻底解决的问题,在这次面向 Vue 3.0 重构时,一次性全部解决。
3354 0
|
Web App开发 JavaScript
vue-cli使用
利用vue-cli快速构建vue单页应用 1.首先电脑上必须已经安装了npm和nodejs 2.在确保电脑已经存在npm和nodejs的情况下,在命令行环境下安装vue-cli npm install vue-cli 这里最好使用(国内的淘宝镜像) cnpm install vue-cli 3.
933 0
|
XML JavaScript 前端开发
技术分享 | 测试平台开发-前端开发之Vue.js 框架
技术分享 | 测试平台开发-前端开发之Vue.js 框架
|
JavaScript
Vue.js 响应式原理
Vue.js 响应式原理
149 0
|
JavaScript 索引
vue数据更新后在视图上不响应
vue数据更新后在视图上不响应
|
XML JavaScript 前端开发
技术分享 | 测试平台开发-前端开发之Vue.js 框架
Vue.js 是一套用于构建用户界面的渐进式框架,在目前的前端开放中比较流行的前端框架。 Vue 被设计成自底向上的逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或已有项目整合。但是学习 Vue.js 需要一定的 HTML、CSS、和 JavaScript 基础,所以本章节将不对这些基础内容进行讲解。 ## Vue的组件化开发 目前的前端开发中组件化开发成为了潮流
|
JavaScript 前端开发
虽然Vue3.0的时代就要带来啦~~今天就接着复习一下Vue2.0叭(一)
时代的几步必然会带动科技的发展,科技的发展必将带来新一波编程语言的到来。在今年,个人认为Vue3.0的时代就会到来,现在我们正处于Vue2.0+Vue3.0的时代,今年Vue3.0必将会替代Vue2.0的存在。
|
JavaScript 容器
后端天天学Vue【一基本结构】
后端天天学Vue【一基本结构】 零 准备工作 一 模板语法 二 数据绑定 三 el和data的其他写法 四 Vue(一)总结
208 0
后端天天学Vue【一基本结构】

热门文章

最新文章