乐趣区

关于javascript:VuePress-博客优化之拓展-Markdown-语法

前言

在《一篇带你用 VuePress + Github Pages 搭建博客》中,咱们应用 VuePress 搭建了一个博客,最终的成果查看:TypeScript 中文文档。

如果咱们浏览过 TypeScript 官网文档,咱们会发现一个很好用的性能,那就是很多代码块,在悬浮下来的时候都会呈现一个 Try 按钮:

点击就会跳转到对应的 Playground,比方图示的按钮跳转的就是这个链接,咱们能够在这个 Playground 批改并验证代码成果。

如果咱们要实现这样的性能,该怎么实现呢?

思考

咱们很容易想到,写一个 VuePress 插件来实现它,这个成果看起来有点像代码复制插件,但细细一想,又非如此。

代码复制插件的实现形式,参考《从零实现一个 VuePress 插件》,能够在页面渲染实现后,遍历每一个代码块而后插入一个复制按钮,点击复制的时候将代码写入剪切板,然而代码块跳转就不一样了,代码跳转须要咱们先写入一个链接地址,而后再渲染按钮,问题是这个链接的地址写在哪里呢?要晓得,咱们能写的只是一个一般的 markdown 文件呀……

于是咱们就想到,是否能够拓展 markdown 的语法呢?就比方失常的代码块写法是:

```typescript
const message = "Hello World!";
```

为了实现这个成果,咱们是否能够这样写:

```typescript
// try-link: https://www.baidu.com
const message = 'Hello World!';
```

然而渲染的时候,并不渲染 try-link 这行正文,而是变成这样的成果:

当点击 Try 的时候,跳转到对应的链接。

当然成果更好的话,能够在鼠标悬浮在代码块上方的时候,才显示这个 Try 按钮,相似于这种成果:

markdown-it

查阅 VuePress 的官网文档,咱们能够晓得:VuePress 应用 markdown-it来渲染 Markdown,那 markdown-it 是什么呢?查阅 markdown-it 的 Github 仓库,能够看到这样一段介绍:

Markdown parser done right. Fast and easy to extend.

简略的来说,markdown-it就是一个 markdown 渲染器,能够将 markdown 渲染成 html 等,而且 markdown-it 反对写插件拓展性能,实际上,VuePress 我的项目中的 markdown 文件为什么能反对写 Vue 组件,就是因为 VuePress 写了插件反对了 Vue 语法,那咱们是不是也能够拓展 markdown 的语法呢?

还好在 VuePress 文档里,提供了配置,能够自定义 markdown-it 插件:

VuePress 应用 markdown-it (opens new window)来渲染 Markdown,上述大多数的拓展也都是通过自定义的插件实现的。想要进一步的话,你能够通过 .vuepress/config.js 的 markdown 选项,来对以后的 markdown-it 实例做一些自定义的配置:

module.exports = {
  markdown: {
    // markdown-it-anchor 的选项
    anchor: {permalink: false},
    // markdown-it-toc 的选项
    toc: {includeLevel: [1, 2] },
    extendMarkdown: md => {
      // 应用更多的 markdown-it 插件!
      md.use(require('markdown-it-xxx'))
    }
  }
}

引入的办法晓得了,但怎么写这个 markdown-it 插件呢?

markdown-it 插件

查阅 markdown-it 的 Github 仓库代码和文档,咱们能够大抵理解到 markdown-it的工作原理,其转换过程相似于 Babel,先转换成形象语法树,而后生成对应的代码,简略的概括就是分为 Parse 和 Render 两个过程。

这点咱们查看源码也能够看到:

MarkdownIt.prototype.render = function (src, env) {env = env || {};
  return this.renderer.render(this.parse(src, env), this.options, env);
};

所以这里咱们解决问题的思路有两个,一个是在 Parse 过程中解决,一个在 Render 过程中解决,为了简略起见,我决定间接解决 Render 过程,查看 Render 的源码,咱们能够看到 Render 里其实曾经依据一些固定的类型写了默认 Rules(渲染规定),就比方对于代码块:

default_rules.fence = function (tokens, idx, options, env, slf) {var token = tokens[idx],
      info = token.info ? unescapeAll(token.info).trim() : '',
      langName = '',
      langAttrs = '',
      highlighted, i, arr, tmpAttrs, tmpToken;

  if (info) {// ...}

  if (options.highlight) {highlighted = options.highlight(token.content, langName, langAttrs) || escapeHtml(token.content);
  } else {highlighted = escapeHtml(token.content);
  }

  if (highlighted.indexOf('<pre') === 0) {return highlighted + '\n';}

  if (info) {//...}


  return  '<pre><code' + slf.renderAttrs(token) + '>'
        + highlighted
        + '</code></pre>\n';
};

咱们能够笼罩这个规定,参照 markdown-it 提供的插件编写准则,咱们能够这样写:

# 获取 md 实例后
md.renderer.rules.fence = function (tokens, idx, options, env, self) {// ...};

为了再省事一点,我筹备间接获取最初渲染的 HTML 后果,它是一个字符串,而后匹配 //try-link: xxx生成的 HTML,替换成一个 <a>链接,咱们查看下 //try-link: xxx这句正文生成的 HTML:

批改下 config.js文件:

module.exports = {
    markdown: {
      extendMarkdown: md => {md.use(function(md) {
          const fence = md.renderer.rules.fence
          md.renderer.rules.fence = (...args) => {let rawCode = fence(...args);
            rawCode = rawCode.replace(/<span class="token comment">\/\/ try-link https:\/\/(.*)<\/span>\n/ig, '<a href="$1"class="try-button"target="_blank">Try</a>');
            return `${rawCode}`
          }
              })
      }
    }
}

这里为了简洁,我没有将 <a>链接的款式间接内联写入其中,而是加了一个类,那在哪里写这个类的款式呢?

VuePress 提供了 docs/.vuepress/styles/index.styl文件,作为将会被主动利用的全局款式文件,会生成在最终的 CSS 文件结尾,具备比默认款式更高的优先级。

所以咱们在 index.styl文件下写入款式:

// 默认款式
.try-button {
    position: absolute;
    bottom: 1em;
    right: 1em;
    font-weight: 100;
    border: 1px solid #719af4;
    border-radius: 4px;
    color: #719af4;
    padding: 2px 8px;
    text-decoration: none;
    transition-timing-function: ease;
    transition: opacity .3s;
    opacity: 0;
}

// hover 款式
.content__default:not(.custom) a.try-button:hover {
    background-color: #719af4;
    color: #fff;
    text-decoration: none;
}

有的时候,主动编译可能不会失效,咱们能够从新运行 yarn run docs:dev

此时曾经能够失常显示按钮了(默认款式透明度为 0,这里为了截图强行设置透明度为 1):

接下来咱们要实现,鼠标悬浮在代码块的时候,才显示这个按钮,这里咱们能够借助《从零实现一个 VuePress 插件》中的办法,在页面 mounted 的时候,获取所有的代码块元素,而后增加事件,咱们再批改下 config.js文件:

module.exports = {
    plugins: [(options, ctx) => {
        return {
          name: 'vuepress-plugin-code-try',
          clientRootMixin: path.resolve(__dirname, 'vuepress-plugin-code-try/index.js')
        }
      }
    ],
    markdown: {
      extendMarkdown: md => {md.use(function(md) {
          const fence = md.renderer.rules.fence
          md.renderer.rules.fence = (...args) => {let rawCode = fence(...args);
            rawCode = rawCode.replace(/<span class="token comment">\/\/ try-link https:\/\/(.*)<\/span>\n/ig, '<a href="$1"class="try-button"target="_blank">Try</a>');
            return `${rawCode}`
          }
              })
      }
    }
}

而后在同级目录 config.js 下新建一个 vuepress-plugin-code-try目录,而后新建一个 index.js文件:

export default {mounted () {setTimeout(() => {document.querySelectorAll('div[class*="language-"] pre').forEach(el => {if (el.querySelector('.try-button')) {el.addEventListener('mouseover', () => {el.querySelector('.try-button').style.opacity = '1';
                })
                el.addEventListener('mouseout', () => {el.querySelector('.try-button').style.opacity = '0';
                })
            }
        })
    }, 100)
  }
}

此时,再运行我的项目,咱们就实现了最后想要的成果:

系列文章

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

  1. 一篇带你用 VuePress + GitHub Pages 搭建博客
  2. 一篇教你代码同步 GitHub 和 Gitee
  3. 还不会用 GitHub Actions?看看这篇
  4. Gitee 如何主动部署 Pages?还是用 GitHub Actions!
  5. 一份前端够用的 Linux 命令
  6. 一份简略够用的 Nginx Location 配置解说
  7. 一篇从购买服务器到部署博客代码的具体教程
  8. 一篇域名从购买到备案到解析的具体教程
  9. VuePress 博客优化之 last updated 最初更新工夫如何设置
  10. VuePress 博客优化之增加数据统计性能
  11. VuePress 博客优化之开启 HTTPS
  12. VuePress 博客优化之开启 Gzip 压缩
  13. 从零实现一个 VuePress 插件

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

如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。

退出移动版