Element 官网显示代码、隐藏代码块
element ui
官网隐藏代码块、显示组件代码块,大家都使用过,但是你知道他是怎么实现的吗?背后涉及到了什么原理?
在没看过具体实现的话我也认为不就一个展开收起代码块得组件嘛?
简单简单分分钟实现一个
实际情况是没那么简单!!! 可以看下如下一段markdown语法:
:::demo 使用type、plain、round和circle属性来定义 Button 的样式。
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button plain>朴素按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button type="success" plain>成功按钮</el-button>
<el-button type="info" plain>信息按钮</el-button>
<el-button type="warning" plain>警告按钮</el-button>
<el-button type="danger" plain>危险按钮</el-button>
</el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
:::
展示效果如下(对,就这段语法就变成了一个可以展示、并且支持复制得代码段):
技术上就是使用markdown
定制:::demo ::::
语法转成了对应得html代码,那么咱们接下来就一步一步分析具体怎么做到的!!!
手动实现组件隐藏、组件显示
咱们现手动拿到
element ui
的demo-block
组件拿到项目里面把相关markdown
的:::demo :::
内容复制给demo-block
组件,看用代码怎么实现一个组件的显示隐藏以及展现的,其实跟你分分钟实现的组件是一样的!!
下面咱们启动一个vue 项目然后把demo-block
引入到项目里面,以及引入element ui
组件,下载highlight.js
包 要用这个展示代码块先看下在elemnetUI
中是怎么用的如下:
// entry.js 中
import hljs from 'highlight.js';
router.afterEach(route => { // router的导航守卫里面
// https://github.com/highlightjs/highlight.js/issues/909#issuecomment-131686186
Vue.nextTick(() => {
const blocks = document.querySelectorAll('pre code:not(.hljs)');
Array.prototype.forEach.call(blocks, hljs.highlightBlock);
});
});
其实跟下面一样的(上面那个是在路由执行后就立刻获取dom 然后调用hljs.highlightBlock
):
<template>
<pre>
<code class="language-js">
{{ code }}
</code>
</pre>
</template>
<script>
import 'highlight.js/styles/monokai-sublime.css';
import hljs from 'highlight.js';
export default {
props: {
code: String,
},
mounted() {
this.highlightCode();
},
methods: {
highlightCode() {
const codeBlocks = this.$el.querySelectorAll('pre code');
codeBlocks.forEach((block) => {
hljs.highlightBlock(block);
});
},
},
};
</script>
是不是highlight.js
怎么使用也学到了!!! 接着往下看
我把demo-block
拿到了项目里面稍微改了下就是把语言啥的都去掉了,在我的项目里面能正常运行了!
还以上面el-button
为例开始使用demo-block
组件如下:
<template>
<div class="app">
<demo-block>
<template #source>
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">
主要按钮
</el-button>
<el-button type="success">
成功按钮
</el-button>
<el-button type="info">
信息按钮
</el-button>
<el-button type="warning">
警告按钮
</el-button>
<el-button type="danger">
危险按钮
</el-button>
</el-row>
<el-row>
<el-button plain>
朴素按钮
</el-button>
<el-button type="primary" plain>
主要按钮
</el-button>
<el-button type="success" plain>
成功按钮
</el-button>
<el-button type="info" plain>
信息按钮
</el-button>
<el-button type="warning" plain>
警告按钮
</el-button>
<el-button type="danger" plain>
危险按钮
</el-button>
</el-row>
<el-row>
<el-button round>
圆角按钮
</el-button>
<el-button type="primary" round>
主要按钮
</el-button>
<el-button type="success" round>
成功按钮
</el-button>
<el-button type="info" round>
信息按钮
</el-button>
<el-button type="warning" round>
警告按钮
</el-button>
<el-button type="danger" round>
危险按钮
</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle />
<el-button type="primary" icon="el-icon-edit" circle />
<el-button type="success" icon="el-icon-check" circle />
<el-button type="info" icon="el-icon-message" circle />
<el-button type="warning" icon="el-icon-star-off" circle />
<el-button type="danger" icon="el-icon-delete" circle />
</el-row>
</template>
<template>
使用
<code>type</code>、
<code>plain</code>、
<code>round</code>和
<code>circle</code>属性来定义 Button 的样式。
</template>
<template #highlight>
<pre>
<code class="html">
{{ code }}
</code>
</pre>
</template>
</demo-block>
</div>
</template>
<script>
import DemoBlock from './components/demo-block.vue';
// import 'highlight.js/styles/monokai-sublime.css';
import hljs from 'highlight.js';
export default {
name: 'App',
components: {
DemoBlock,
},
data() {
return {
code: `<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button plain>朴素按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button type="success" plain>成功按钮</el-button>
<el-button type="info" plain>信息按钮</el-button>
<el-button type="warning" plain>警告按钮</el-button>
<el-button type="danger" plain>危险按钮</el-button>
</el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>`
};
},
mounted() {
this.highlightCode();
},
methods: {
highlightCode() {
const codeBlocks = this.$el.querySelectorAll('pre code');
codeBlocks.forEach((block) => {
hljs.highlightBlock(block);
});
},
},
};
</script>
效果如下图(样式就不调整了,基本都出来了):
demo-block
分了3个插槽一个是默认插槽描述,第二个插槽是界面展示效果,第三个插槽也就是让用户复制的代码
<template>
<demo-block>
<template>描述信息</template>
<template #source>源码展示效果</template>
<template #highlight>源码高亮让支持复制</template>
</dome-block>
</template>
markdown 转成对应的html
怎么才能像咱们手动复制样把对应的
markdown
转成对应的vue
呢! 编写webpack
插件loader
实现转换,咱们先改下配置使用element ui
内置编好的loader
先用,先实现效果,后面再一步步分析怎么编写这个loader
的。更改配置如下:
第一步:编写这个loader用了几个插件咱们先下载如下:
{
"transliteration": "^1.1.11",
"markdown-it": "^8.4.1",
"markdown-it-anchor": "^5.0.2",
"markdown-it-chain": "^1.3.0",
"markdown-it-container": "^2.0.0"
}
第二步:拿了一下官网md-loader
文件夹到我本地准备引入vue
第三步:vue.config.js配置如下:
const path = require('path');
module.exports = {
configureWebpack: (config) => {
config.module.rules.push(
{
test: /\.md$/,
use: [
{
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
}
}
},
{
loader: path.resolve(__dirname, './md-loader/index.js')
}
]
},
)
},
}
第四步:我拿了个官网的button.md
放在了跟App.vue
同级下,替换掉了原App.vue
代码如下:
<template>
<div>
<button-docs />
</div>
</template>
<script>
import ButtonDocs from './button.md';
export default {
name: 'App',
components: {
ButtonDocs,
}
};
</script>
<style lang="scss">
.demo-block.demo-button {
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.el-button + .el-button {
margin-left: 10px;
}
.el-button-group {
.el-button + .el-button {
margin-left: 0;
}
& + .el-button-group {
margin-left: 10px;
}
}
}
</style>
展示效果如下:
可以看的出来都展示出来了,这下知道
element ui
的官网是怎么做的了吧!!
解析深挖my-loader
(一) 使用的插件解析
使用的插件解析:咱们进一步分析一下
md-loader
是如何实现具体markdown to vue
的。 先了解下它用了的那些插件都是干啥的如下:
markdown-it
是一个基于JavaScript
的Markdown
解析器,可以将Markdown
文本转换成HTML
标签。
与其他Markdown
解析器相比,markdown-it
具有以下优点:
- 快速高效:
markdown-it
使用了高效的解析算法,相比其他解析器能够更快地处理大量的Markdown
文本。 - 灵活可扩展:
markdown-it
的插件机制非常灵活,用户可以根据需要自定义和扩展不同的解析规则。 - 易于使用:
markdown-it
提供了简单易用的API
,让用户可以在几行代码内实现Markdown
到HTML
的转换。
简单使用示例:
const md = require('markdown-it')();
const result = md.render('# Hello, World!')
console.log(result) // 输出 '<h1>Hello, World!</h1>'
transliteration
是作为中文转换的如下:
import { transliterate as tr, slugify } from 'transliteration';
tr('你好, world!');
// Ni Hao , world!
slugify('你好, world!');
// ni-hao-world
-
markdown-it-anchor
为markdown-it
设计的标题锚点的生成。这个插件处理锚点生成,不是解析markdown
的主要流程,可以暂时不去关注,知道功能就可以了,后面有时间可以看看里面的源码。 -
markdown-it-chain
是干什么的呢? 解说如下:
markdown-it-chain
是一个用于构建 markdown-it
插件链的工具库,它通过一种链式调用的方式来简化插件的配置和使用过程,使得用户可以更加方便地定义自己的 Markdown
解析规则。
具体来说,markdown-it-chain
可以将多个 markdown-it
插件组合起来,形成一个完整的解析链,并提供了一些常用的 API
和钩子函数,帮助用户在解析过程中添加、修改或删除某些解析规则。同时,由于 markdown-it-chain
使用了链式调用的方式,因此用户可以通过连续调用多个方法来按顺序设置不同的插件参数,从而实现高度定制化的解析效果。
总之,markdown-it-chain
旨在让用户更加方便地配置和使用 markdown-it
插件,提高其 Markdown
解析效率和灵活性。
使用方式如下:
config
.options.html(true).end()
.plugin('anchor').use(anchorPlugin, [
{
level: 2,
slugify: slugify,
permalink: true,
permalinkBefore: true
}
]).end()
.plugin('containers').use(containers).end();
const md = config.toMd();
markdown-it-container
是什么呢?
markdown-it-container
是一个用于在 Markdown
解析器中创建自定义块级容器的插件。通过使用 markdown-it-container
插件,用户可以方便地定义自己的容器格式,并在解析过程中将其转换成相应的 HTML 标签。
就是解析::: demo ::::、::: AAA ::: …这个的!!!!用3个冒号包裹叫做自定义块级容器
。
具体来说
markdown-it-container
可以让用户对指定的Markdown
文本进行标记,将其包裹在自定义的块级容器中,并为该容器添加任意的 CSS 类名和样式。这样做既能提高文章的可读性,又能使其呈现出更美观的排版效果。
使用示例如下:
const md = require('markdown-it')();
const container = require('markdown-it-container');
// 定义自定义容器
md.use(container, 'AAA', {
render: function (tokens, idx) {
const token = tokens[idx]
if (token.nesting === 1) {
// 开始标记
return `<div class="my-aaa-container">`;
} else {
// 结束标记
return '</div>';
}
}
});
// 使用自定义容器
const result = md.render(`
::: AAA
这是a AAA 容器块
:::
`); // 输出 <div class="my-aaa-container"><p>这是a AAA 容器块</p> </div>
经过上面分析每个插件怎么使用的,咱们再去读代码就更会清晰了。
解析深挖my-loader
(二) 源码解析
源码解析:咱们进一步分析一下
md-loader
是如何实现具体markdown to vue
的。
md-loader
总共分了4个文件如下:
|--
|-- config.js
|-- containers.js
|-- fence.js
|-- index.js
|-- util.js
containers.js
中下面这段代码很清晰了就是把:::demo 描述信息 内容部分 :::
替换成了
<demo-block>
<div>描述信息<div>
<!--element-demo: 内容部分 :element-demo-->
</demo-block>
containers.js
代码如下:
const mdContainer = require('markdown-it-container');
module.exports = md => {
md.use(mdContainer, 'demo', {
validate(params) {
return params.trim().match(/^demo\s*(.*)$/);
},
render(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
if (tokens[idx].nesting === 1) {
const description = m && m.length > 1 ? m[1] : '';
const content = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
return `<demo-block>
${description ? `<div>${md.render(description)}</div>` : ''}
<!--element-demo: ${content}:element-demo-->
`;
}
return '</demo-block>';
}
});
md.use(mdContainer, 'tip');
md.use(mdContainer, 'warning');
};
config.js
是使用markdown-it-chain
把插件markdown-it-anchor
生成锚点和containers
串起来了最后调用了下fence.js
如下:
const Config = require('markdown-it-chain');
const anchorPlugin = require('markdown-it-anchor');
const slugify = require('transliteration').slugify;
const containers = require('./containers');
const overWriteFenceRule = require('./fence');
const config = new Config();
config
.options.html(true).end()
.plugin('anchor').use(anchorPlugin, [
{
level: 2,
slugify: slugify,
permalink: true,
permalinkBefore: true
}
]).end()
.plugin('containers').use(containers).end();
const md = config.toMd();
overWriteFenceRule(md);
module.exports = md;
fence.js
是把:::demo 内容部分:::
内容部分生成了如下:
<template slot="highlight">
<pre v-pre>
<code class="html">
{{ md.utils.escapeHtml(内容部分) }}
</code>
</pre>
<template slot="highlight">
md.utils.escapeHtml
类似咱们前面使用highlight.js
把代码转成html
的代码展示。
fence.js
代码如下:
// 覆盖默认的 fence 渲染策略
module.exports = md => {
const defaultRender = md.renderer.rules.fence;
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx];
// 判断该 fence 是否在 :::demo 内
const prevToken = tokens[idx - 1];
const isInDemoContainer = prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/);
if (token.info === 'html' && isInDemoContainer) {
return `<template slot="highlight"><pre v-pre><code class="html">${md.utils.escapeHtml(token.content)}</code></pre></template>`;
}
return defaultRender(tokens, idx, options, env, self);
};
};
经过咱们上面一部分render
后咱们的button.md
也就变成了如下:
<h2 id="button-an-niu"><a class="header-anchor" href="#button-an-niu">¶</a> Button 按钮</h2>
<p>常用的操作按钮。</p>
<h3 id="ji-chu-yong-fa"><a class="header-anchor" href="#ji-chu-yong-fa">¶</a> 基础用法</h3>
<p>基础的按钮用法。</p>
<demo-block>
<div><p>使用<code>type</code>、<code>plain</code>、<code>round</code>和<code>circle</code>属性来定义 Button 的样式。</p>
</div>
<!--element-demo: <el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button plain>朴素按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button type="success" plain>成功按钮</el-button>
<el-button type="info" plain>信息按钮</el-button>
<el-button type="warning" plain>警告按钮</el-button>
<el-button type="danger" plain>危险按钮</el-button>
</el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button plain>朴素按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button type="success" plain>成功按钮</el-button>
<el-button type="info" plain>信息按钮</el-button>
<el-button type="warning" plain>警告按钮</el-button>
<el-button type="danger" plain>危险按钮</el-button>
</el-row>
<el-row>
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
</code></pre></template></demo-block><h3 id="jin-yong-zhuang-tai"><a class="header-anchor" href="#jin-yong-zhuang-tai">¶</a> 禁用状态</h3>
<p>按钮不可用状态。</p>
<demo-block>
<div><p>你可以使用<code>disabled</code>属性来定义按钮是否可用,它接受一个<code>Boolean</code>值。</p>
</div>
<!--element-demo: <el-row>
<el-button disabled>默认按钮</el-button>
<el-button type="primary" disabled>主要按钮</el-button>
<el-button type="success" disabled>成功按钮</el-button>
<el-button type="info" disabled>信息按钮</el-button>
<el-button type="warning" disabled>警告按钮</el-button>
<el-button type="danger" disabled>危险按钮</el-button>
</el-row>
<el-row>
<el-button plain disabled>朴素按钮</el-button>
<el-button type="primary" plain disabled>主要按钮</el-button>
<el-button type="success" plain disabled>成功按钮</el-button>
<el-button type="info" plain disabled>信息按钮</el-button>
<el-button type="warning" plain disabled>警告按钮</el-button>
<el-button type="danger" plain disabled>危险按钮</el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-row>
<el-button disabled>默认按钮</el-button>
<el-button type="primary" disabled>主要按钮</el-button>
<el-button type="success" disabled>成功按钮</el-button>
<el-button type="info" disabled>信息按钮</el-button>
<el-button type="warning" disabled>警告按钮</el-button>
<el-button type="danger" disabled>危险按钮</el-button>
</el-row>
<el-row>
<el-button plain disabled>朴素按钮</el-button>
<el-button type="primary" plain disabled>主要按钮</el-button>
<el-button type="success" plain disabled>成功按钮</el-button>
<el-button type="info" plain disabled>信息按钮</el-button>
<el-button type="warning" plain disabled>警告按钮</el-button>
<el-button type="danger" plain disabled>危险按钮</el-button>
</el-row>
</code></pre></template></demo-block><h3 id="wen-zi-an-niu"><a class="header-anchor" href="#wen-zi-an-niu">¶</a> 文字按钮</h3>
<p>没有边框和背景色的按钮。</p>
<demo-block>
<!--element-demo: <el-button type="text">文字按钮</el-button>
<el-button type="text" disabled>文字按钮</el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-button type="text">文字按钮</el-button>
<el-button type="text" disabled>文字按钮</el-button>
</code></pre></template></demo-block><h3 id="tu-biao-an-niu"><a class="header-anchor" href="#tu-biao-an-niu">¶</a> 图标按钮</h3>
<p>带图标的按钮可增强辨识度(有文字)或节省空间(无文字)。</p>
<demo-block>
<div><p>设置<code>icon</code>属性即可,icon 的列表可以参考 Element 的 icon 组件,也可以设置在文字右边的 icon ,只要使用<code>i</code>标签即可,可以使用自定义图标。</p>
</div>
<!--element-demo: <el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
<el-button type="primary" icon="el-icon-search">搜索</el-button>
<el-button type="primary">上传<i class="el-icon-upload el-icon--right"></i></el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
<el-button type="primary" icon="el-icon-search">搜索</el-button>
<el-button type="primary">上传<i class="el-icon-upload el-icon--right"></i></el-button>
</code></pre></template></demo-block><h3 id="an-niu-zu"><a class="header-anchor" href="#an-niu-zu">¶</a> 按钮组</h3>
<p>以按钮组的方式出现,常用于多项类似操作。</p>
<demo-block>
<div><p>使用<code><el-button-group></code>标签来嵌套你的按钮。</p>
</div>
<!--element-demo: <el-button-group>
<el-button type="primary" icon="el-icon-arrow-left">上一页</el-button>
<el-button type="primary">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</el-button-group>
<el-button-group>
<el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
</el-button-group>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-button-group>
<el-button type="primary" icon="el-icon-arrow-left">上一页</el-button>
<el-button type="primary">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</el-button-group>
<el-button-group>
<el-button type="primary" icon="el-icon-edit"></el-button>
<el-button type="primary" icon="el-icon-share"></el-button>
<el-button type="primary" icon="el-icon-delete"></el-button>
</el-button-group>
</code></pre></template></demo-block><h3 id="jia-zai-zhong"><a class="header-anchor" href="#jia-zai-zhong">¶</a> 加载中</h3>
<p>点击按钮后进行数据加载操作,在按钮上显示加载状态。</p>
<demo-block>
<div><p>要设置为 loading 状态,只要设置<code>loading</code>属性为<code>true</code>即可。</p>
</div>
<!--element-demo: <el-button type="primary" :loading="true">加载中</el-button>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-button type="primary" :loading="true">加载中</el-button>
</code></pre></template></demo-block><h3 id="bu-tong-chi-cun"><a class="header-anchor" href="#bu-tong-chi-cun">¶</a> 不同尺寸</h3>
<p>Button 组件提供除了默认值以外的三种尺寸,可以在不同场景下选择合适的按钮尺寸。</p>
<demo-block>
<div><p>额外的尺寸:<code>medium</code>、<code>small</code>、<code>mini</code>,通过设置<code>size</code>属性来配置它们。</p>
</div>
<!--element-demo: <el-row>
<el-button>默认按钮</el-button>
<el-button size="medium">中等按钮</el-button>
<el-button size="small">小型按钮</el-button>
<el-button size="mini">超小按钮</el-button>
</el-row>
<el-row>
<el-button round>默认按钮</el-button>
<el-button size="medium" round>中等按钮</el-button>
<el-button size="small" round>小型按钮</el-button>
<el-button size="mini" round>超小按钮</el-button>
</el-row>
:element-demo-->
<template slot="highlight"><pre v-pre><code class="html"><el-row>
<el-button>默认按钮</el-button>
<el-button size="medium">中等按钮</el-button>
<el-button size="small">小型按钮</el-button>
<el-button size="mini">超小按钮</el-button>
</el-row>
<el-row>
<el-button round>默认按钮</el-button>
<el-button size="medium" round>中等按钮</el-button>
<el-button size="small" round>小型按钮</el-button>
<el-button size="mini" round>超小按钮</el-button>
</el-row>
</code></pre></template></demo-block><h3 id="attributes"><a class="header-anchor" href="#attributes">¶</a> Attributes</h3>
<table>
<thead>
<tr>
<th>参数</th>
<th>说明</th>
<th>类型</th>
<th>可选值</th>
<th>默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td>size</td>
<td>尺寸</td>
<td>string</td>
<td>medium / small / mini</td>
<td>—</td>
</tr>
<tr>
<td>type</td>
<td>类型</td>
<td>string</td>
<td>primary / success / warning / danger / info / text</td>
<td>—</td>
</tr>
<tr>
<td>plain</td>
<td>是否朴素按钮</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>round</td>
<td>是否圆角按钮</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>circle</td>
<td>是否圆形按钮</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>loading</td>
<td>是否加载中状态</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>disabled</td>
<td>是否禁用状态</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>icon</td>
<td>图标类名</td>
<td>string</td>
<td>—</td>
<td>—</td>
</tr>
<tr>
<td>autofocus</td>
<td>是否默认聚焦</td>
<td>boolean</td>
<td>—</td>
<td>false</td>
</tr>
<tr>
<td>native-type</td>
<td>原生 type 属性</td>
<td>string</td>
<td>button / submit / reset</td>
<td>button</td>
</tr>
</tbody>
</table>
到这里是不是都能看得懂了! 别着急还有 这才是把界面都展示了但是少了个界面
<template #source>组件展示样式</template>
解析深挖my-loader
(三) 源码解析之vue编译
源码解析之vue编译:咱们进一步分析一下
md-loader
是如何实现具体markdown to vue
的。
看咱们的index.js
如下:
const {
stripScript,
stripTemplate,
genInlineComponentText
} = require('./util');
const md = require('./config');
module.exports = function(source) {
const content = md.render(source);
console.log(content,'content...'); // 这里就是咱们上面打印的
const startTag = '<!--element-demo:';
const startTagLen = startTag.length;
const endTag = ':element-demo-->';
const endTagLen = endTag.length;
let componenetsString = '';
let id = 0; // demo 的 id
let output = []; // 输出的内容
let start = 0; // 字符串开始位置
let commentStart = content.indexOf(startTag);
let commentEnd = content.indexOf(endTag, commentStart + startTagLen);
while (commentStart !== -1 && commentEnd !== -1) {
output.push(content.slice(start, commentStart));
const commentContent = content.slice(commentStart + startTagLen, commentEnd);
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
let demoComponentContent = genInlineComponentText(html, script);
const demoComponentName = `element-demo${id}`;
output.push(`<template slot="source"><${demoComponentName} /></template>`);
componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;
// 重新计算下一次的位置
id++;
start = commentEnd + endTagLen;
commentStart = content.indexOf(startTag, start);
commentEnd = content.indexOf(endTag, commentStart + startTagLen);
}
// 仅允许在 demo 不存在时,才可以在 Markdown 中写 script 标签
// todo: 优化这段逻辑
let pageScript = '';
if (componenetsString) {
pageScript = `<script>
export default {
name: 'component-doc',
components: {
${componenetsString}
}
}
</script>`;
} else if (content.indexOf('<script>') === 0) { // 硬编码,有待改善
start = content.indexOf('</script>') + '</script>'.length;
pageScript = content.slice(0, start);
}
output.push(content.slice(start));
return `
<template>
<section class="content element-doc">
${output.join('')}
</section>
</template>
${pageScript}
`;
};
具体怎么做的呢! 其实就是查找indexOf
<!--element-demo: 内容 :element-demo-->';
开始、闭合的索引然后呢使用stripTemplate
和stripScript
方法去生成对应的template、script
代码
let componenetsString = '';
const commentContent = `<el-button>内容</el-button>....<el-button>内容</el-button>`;
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
// 封成一个个组件
const demoComponentName = `element-demo${id}`;
// 供给去使用 如下:
output.push(`<template slot="source"><${demoComponentName} /></template>`);
// 这个是最后放在script的 components中去注册组件的。
componenetsString += `${JSON.stringify(demoComponentName)}: ${demoComponentContent},`;
// 这里是写了个基础模板
pageScript = `<script>
export default {
name: 'component-doc',
components: {
${componenetsString}
}
}
</script>`;
return `
<template>
<section class="content element-doc">
${output.join('')}
</section>
</template>
${pageScript}
`;
咱们再看下把button.md
整个转成了个什么样的vue
如下(删除了部分不主要的代码):
<template>
<section class="content element-doc">
<template slot="source"><element-demo0 /></template>
</section>
</template>
<script>
export default {
name: 'component-doc',
components: {
"element-demo0": (function() {
var render = function () {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c(
"div",
[
_c(
"el-row",
[
_c("el-button", [_vm._v("默认按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary" } }, [_vm._v("主要按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success" } }, [_vm._v("成功按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info" } }, [_vm._v("信息按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning" } }, [_vm._v("警告按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger" } }, [_vm._v("危险按钮")]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { plain: "" } }, [_vm._v("朴素按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary", plain: "" } }, [
_vm._v("主要按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success", plain: "" } }, [
_vm._v("成功按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info", plain: "" } }, [
_vm._v("信息按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning", plain: "" } }, [
_vm._v("警告按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger", plain: "" } }, [
_vm._v("危险按钮"),
]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { round: "" } }, [_vm._v("圆角按钮")]),
_vm._v(" "),
_c("el-button", { attrs: { type: "primary", round: "" } }, [
_vm._v("主要按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "success", round: "" } }, [
_vm._v("成功按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "info", round: "" } }, [
_vm._v("信息按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "warning", round: "" } }, [
_vm._v("警告按钮"),
]),
_vm._v(" "),
_c("el-button", { attrs: { type: "danger", round: "" } }, [
_vm._v("危险按钮"),
]),
],
1
),
_vm._v(" "),
_c(
"el-row",
[
_c("el-button", { attrs: { icon: "el-icon-search", circle: "" } }),
_vm._v(" "),
_c("el-button", {
attrs: { type: "primary", icon: "el-icon-edit", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "success", icon: "el-icon-check", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "info", icon: "el-icon-message", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "warning", icon: "el-icon-star-off", circle: "" },
}),
_vm._v(" "),
_c("el-button", {
attrs: { type: "danger", icon: "el-icon-delete", circle: "" },
}),
],
1
),
],
1
)
}
var staticRenderFns = []
render._withStripped = true
const democomponentExport = {}
return {
render,
staticRenderFns,
...democomponentExport
}
})(),
}
}
</script>
可以看到了吧!!! 知道这几行代码是干啥得了把:
const html = stripTemplate(commentContent);
const script = stripScript(commentContent);
let demoComponentContent = genInlineComponentText(html, script);
加油剩不多了!接着分析stripTemplate
、stripScript
、genInlineComponentText
、就完事了,相关函数代码如下
-
stripTemplate
、stripScript
就是截取到template
script
的相关内容去除前后空格换行。 -
genInlineComponentText
关键如下:
const { compileTemplate } = require('@vue/component-compiler-utils');
const compiler = require('vue-template-compiler');
function genInlineComponentText(template, script) {
// https://github.com/vuejs/vue-loader/blob/423b8341ab368c2117931e909e2da9af74503635/lib/loaders/templateLoader.js#L46
const finalOptions = {
source: `<div>${template}</div>`,
filename: 'inline-component', // TODO:这里有待调整
compiler
};
const compiled = compileTemplate(finalOptions);
let demoComponentContent = `
${compiled.code}
`;
// todo: 这里采用了硬编码有待改进
script = script.trim();
if (script) {
script = script.replace(/export\s+default/, 'const democomponentExport =');
} else {
script = 'const democomponentExport = {}';
}
demoComponentContent = `(function() {
${demoComponentContent}
${script}
return {
render,
staticRenderFns,
...democomponentExport
}
})()`;
return demoComponentContent;
}
总结
咱们先从自己拿
demo-block
组件自己手动生成HTML
组件代码, 再到使用官网md-loader
插件解析button.md
,再到一步步解析md-loader
。 总算是把Markdown -> vue
完成了!!! 期间也学会了使用markdown-it
等相关插件使用。 保持好奇心、保持好学心 冲冲!!!
原文链接:https://juejin.cn/post/7241567583505481783 作者:三原