共计 4997 个字符,预计需要花费 13 分钟才能阅读完成。
前言
在《一篇带你用 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、集体服务器等平台。
- 一篇带你用 VuePress + GitHub Pages 搭建博客
- 一篇教你代码同步 GitHub 和 Gitee
- 还不会用 GitHub Actions?看看这篇
- Gitee 如何主动部署 Pages?还是用 GitHub Actions!
- 一份前端够用的 Linux 命令
- 一份简略够用的 Nginx Location 配置解说
- 一篇从购买服务器到部署博客代码的具体教程
- 一篇域名从购买到备案到解析的具体教程
- VuePress 博客优化之 last updated 最初更新工夫如何设置
- VuePress 博客优化之增加数据统计性能
- VuePress 博客优化之开启 HTTPS
- VuePress 博客优化之开启 Gzip 压缩
- 从零实现一个 VuePress 插件
微信:「mqyqingfeng」,加我进冴羽惟一的读者群。
如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。