yliu

时来天地皆同力,运去英雄不自由

VuePress插件开发不完全指南


最近用 vuepress 把社区的 typeScript 翻译文章打包成了一个在线文档,不过有一些插件社区没有或者实现的不符合自己定制需求,所以决定自定义实现一个,这篇文化以掘金复制代码功能为例子。 先看一下开发完成的界面

1

先看一下官方给出的架构图 是不是感觉有点头疼,这里记不住没有关系,我们只需要知道插件是在 node 环境下运行,所以我们插件的返回形式必须是CommonJS形式。

与 vue 类似,vuepress也有生命周期

  • ready

可以简单理解为初始化完成调用

  • updated

页面更新调用

  • generated

生产环境构建完成调用

实现思路

因为vuepress会将 md 文件打包成多份 html 文件,所以在每次文件地址变更的时候我们都需要知道这个变更之后更新我们的组件,根据上面的生命周期可以在updated实现我们的需求

至于如何将复制粘贴的组件插入到指定的code中,我们可以在页面加载完成后,搜索所有的指定节点,之后通过appendChild将组件插入

项目结构

copy
├─ clientRootMixin.js
├─ clipboard.js
├─ copy.vue
└─ index.js
  • index.js

暴露的出口文件

  • copy.vue

具体实现复制代码组件

  • clipboard.js

负责实现置剪切板文本

  • clientRootMixin.js

负责实现将组件插入到不同的页面之中

index.js

上面说了我们要开发一个复制代码的插件,我们就先按照简单的做法定义三个参数

  • 第一个是选择器的范围
  • 第二个是复制代码显示的文本
  • 第三个则是回调函数,负责接收变更消息,实现自定义的动画效果

在官方给出的例子,有两种方式使用插件

// 例1
module.exports = {
  plugins: [
    [
      "vuepress-plugin-xxx",
      {
        /* options */
      }
    ]
  ]
};
// 例2
module.exports = {
  plugins: {
    xxx: {
      /* options */
    }
  }
};

可以看到,如果有参数的话可以这样传递,那么我们定义的插件第一步就是要处理这个参数,当然也可以不接受,如果不接受直接返回一个对象就可以了

module.exports = {
  // ...
};

下面来定义一个简单接受options的函数

// 对象式

module.exports = {
  define: {
    selector: options.selector || 'div[class*="language-"] pre',
    copyText: options.copyText || "复制代码",
    change: options.change
  }
};
// 函数式:
module.exports = (options, context) => ({
  define() {
    return {
      selector: options.selector || 'div[class*="language-"] pre',
      copyText: options.copyText || "复制代码",
      change: options.change
    };
  }
});

注意必须返回是CommonJS形式,上面我们通过define属性来定义供我们插件内部使用的全局变量,他支持函数和对象两种形式,可以理解为 vue 的 data 属性

这一步比较简单,就不做其他过多说明了,上面我们提到了需要将组件注入到所有页面中,这一步是clientRootMixin.js所需要负责的事情,下面就是实现它,我们需要在index.js引入它,一个完整的index.js看起来应该是这样

const path = require("path");

module.exports = (options = {}, ctx) => ({
  define: {
    // ...
  },
  clientRootMixin: path.resolve(__dirname, "clientRootMixin.js")
});

clientRootMixin.js

clientRootMixin 可以让我们控制根文组件的生命周期,这里我们只需要监听updated事件,之后把copy.vue插入到当前页面内即可

import CodeCopy from "./copy.vue";
import Vue from "vue";

export default {
  updated() {
    // 等待dom加载完成之后执行
    this.$nextTick(() => {
      this.update();
    });
  },
  methods: {
    update() {
      // 获取所有的dom,之后在所有的代码块上插入vue的组件
      const dom = Array.from(document.querySelectorAll(selector));
      dom.forEach(el => {
        // 判断一下,当前节点是不是已经插入了
        if (/v-copy/.test(el.className)) {
          return;
        }
        // 创建copy组件
        const C = Vue.extend(CodeCopy);
        const copy = new C();
        // 下面这些是组件的props以及一些私有属性
        copy.copyText = copyText;
        copy.code = el.textContent;
        copy._parent = el;
        copy.$mount();
        el.className += ` v- copy`;
        el.appendChild(copy.$el);
      });
    }
  }
};

OK,到这一步也完成了,下面就是怎么把代码置入剪切板了

clipboard.js

  • navigator.clipboard 支持异步剪贴板
  • document.execCommand() 兼容性比较好一些,但是只能同步剪贴板

上面是两种原生的方式,不过这里因为是作为库使用,需要考虑兼容性问题,所以我选择了已经封装好的clipboard.js作为实现复制粘贴,下面是具体的封装方法,这一步可以跳过

import ClipboardJS from "clipboard";
// 封装的剪切板事件
const btn = document.createElement("div");
btn.style.display = "none";
document.body.appendChild(btn);

function setUpText(text = "") {
  return new Promise((resolve, reject) => {
    const cli = new ClipboardJS(btn, {
      text() {
        return text;
      }
    });
    // 触发点击事件\
    const click = new Event("click");
    cli.on("success", function() {
      resolve(text);
      // 无论成功与否都删除
      cli.destroy();
    });

    cli.on("error", function(e) {
      reject(e.action);
      // 无论成功与否都删除
      cli.destroy();
    });
    btn.dispatchEvent(click);
  });
}

export default setUpText;

OK,到这一步基本上就完成了准备工作,下面就回归我们熟悉的 vue 组件开发了

copy.vue

这一步比较简单,就是定义一些 css 属性点击按钮的时候执行clipboard.js就行了

<template>
  <span>
    <span ref="btn" class="v-copy-code-btn" @click="copyClick">{{
      copyText
    }}</span>
  </span>
</template>

<script>
import clipboard from "./clipboard";
export default {
  props: {
    copyText: {
      type: String,
      default: "复制代码"
    },
    code: String
  },
  methods: {
    copyClick() {
      clipboard(this.code);
      // 执行复制具体实现省略
    }
  }
};
</script>

<style>
<!-- 省略 -->
</style>

最后

撒花,完成这一步之后就是将代码发布到 npm 上供大家使用,这个过程就不再描述了。

最后本人目前准备找工作,有小伙伴内推一下么,不胜感激