NutUI 是一款很是优秀的移动端组件库, GitHub 上已得到 1.8k 的 star,NPM 下载量超过 13k。公司内部已赋能支持 40+ 个项目,外部接入使用项目达到 20+ 个。使用者将会得到以下收益:css
NutUI 的历史已经有 2 年了,2017 年的版本是 v1.0 ,那是一个造轮子和摸索的过程。最开始的组件大都是来源于业务,项目中通过抽离封装作成的组件。html
那仍是一我的人均可以提交组件的年代,当时最朴素的一个想法就是复用,先把业务中组件数量积累下来。好比一个地址配送组件一个同窗开发花了2-3人日,在另外一个购物场景项目中也会有,若是每人都去开发一个必然浪费。最先组件库仅仅是在公司内部使用,那时候的 NutUi是下面这个样子:前端
早期的首页:vue
早期的文档页:node
经过上面两张图咱们能够看到,当初的网站有不少不足乃至成为痛点,具体表现如下几点:webpack
针对这些痛点 2.0 做出以下改变:git
对比 1.0 咱们的组件库有了全面的提高,代码规范简约,可维护性强,组件使用场景考虑充分,功能更加完善。那么这么多的组件,它的官方网站是怎么构建的呢?本文将为你揭秘。github
经过这篇文章后你会了解 NutUI 组件库官方网站的开发流程。同时文章里面咱们详细分析了为何选用 Webpack 插件的这种形式去开发,以及 .md
转 .vue
这种方式的好处。web
在开发这个插件功能中,咱们还用到了一些 Node 操做,和 NPM 依赖包,它和咱们平时的前端开发有很多区别,不一样于页面的交互逻辑,我感受这更有意思,那么我们就一探究竟吧。npm
在文章开始以前,咱们先介绍下这个插件的使用方法,这有助于你理解咱们实现这个功能的思路。
总的来讲,它是一个 Webpack 插件,因此在使用上只需在 Webpack 中配置,具体以下:
{
[
...new mdtohtml({
entry: "./docs",
output: "./sites/doc/page/",
template: "./doc-site/template.html",
nav: "left",
needCode: false,
isProduction: isDev
})
];
}
复制代码
属性说明
参数 | 值 | 说明 |
---|---|---|
entry | string | 须要处理文件的文件夹路径 |
output | string | 处理完成后输出的文件路径 |
template | string | 转换成 .vue 以后须要配置的 HTML 模版路径 |
nav | string | 生成网站导航的位置,目前只支持左边 |
needCode | boolean | 是否须要代码显示工具 |
isProduction | boolean | 是开发环境仍是编译环境 |
咱们 NutUI 的官方网站的需求是什么呢?
了解了具体需求,下面就能够开发功能了,其中最重要的就是选择一条对的路。
这里我选择的是经过 .md
转换成 .vue
,为何呢?
语法简单,即便是非开发者也能快速上手。全部 MD 标记都是基于这四个符号(* - +. >)或组合,而 HTML 的标签浩如烟海,很差记还会写一些没有样式的标签。
组件库文档会有不少的代码展现和样式展现,使用 HTML 标签很差控制而使用 MD 就会方便不少,能够轻松的控制代码展现格式。咱们想展现一段 CSS 代码或者 JS 代码只要使用 ```js``` 或者 ```css``` 就能够作代码的展现。
容易使用固定的模版,让编写文档的人按照模版去编写文档,更容易让文档统一。
MD 文档不像 HTML 标签同样须要严格的闭合,这也是选择 MD 开发的缘由之一。
基于 MD 转换成 HTML 的 NPM 包市面上有不少,在处理这部分上面咱们能够节省不少工做。
首先 NutUI 组件是基于 Vue 的,在同一个构建工具下,换一种框架成本过高。
采用 .vue
模版开发正好能够把 .md
转换过来的 HTML 直接嵌套到 template 中。
模块式的开发有利于对每一个组件进行统一管理。
咱们的 Style 不多使用 Class ,而是基于标签选择去作样式处理:
h1,h2,p
{
color: #333333;
}
h1
{
font-size: 30px;
font-weight: 700;
margin: 10px 0 20px;
}
复制代码
这样的好处就是咱们在编写文档时不用去关心这些,只须要记住简单的几个 MD 语法就能够写出一篇相对完整的文档。
基本书写格式以下:
首先感谢 AST 让咱们能够实现这个功能,下面咱们先看下它是如何进行转换的,咱们不只仅会开车,还要学会修车。同时它也是目前市面上各类代码转换工具的基础。话很少说,先开车了。
首先咱们举个简单的例子,下面是一段 MD 格式的片断:
## marked 转换
### marked 转换
\`\`\`js
var a = 0;
var b = 2;
\`\`\`
复制代码
经过 AST 处理结果,以下:
它处理的结果是一个大的对象,每一个节点都有特定的 Type ,咱们根据这些内容就能够进行处理,从新生成一份咱们想要的格式。
经过上面的图片咱们能够看到: ## 的 Type 为 heading , depth:2
经过这个能够理解为这是一个 h2 标签,而 ``` 的 Type 为 Code , 咱们写在 ```里面的内容都放在 Code 这个对象里面。
它的结构就像一颗大树,有不一样的枝干,我想这也是为何 AST 被称为抽象语法树了。经过处理生成的 AST 对象大致结构你们能够参考下图:
接下来咱们看下详细的转换,例如咱们这个项目里面是须要把它转换为 HTML。
咱们就是经过递归的方式去处理这个对象的结构,把它们转换成想要的文本。 在 NPM 包里面也有不少工具包能够帮我去作处理这个对象,例如: 经过 estraverse 这个插件库去遍历 AST 对象的全部节点:
const res = estraverse.traverse(ast, {
enter: function (node, parent) {
if (node.type == "heading") return "";
},
leave: function (node, parent) {
if (node.type == "code") console.log(node.id.name);
}
});
复制代码
说明: 经过 estraverse.traverse
这个方法去遍历整个 AST 对象。 在遍历的过程当中它接受一个 option
,其中有两个属性分别是 enter
和 leave
。它们分别表明监听遍历的进入阶段和离开阶段。一般咱们只须要定义 enter
里面的方法就好,例如上面的例子,当条件知足的时候咱们去执行某些咱们想要的处理方式。
上面仅仅是对代码转换过程的一个简单的模拟,而实际开发过程当中咱们能够借助封装好的工具去完成上面的事情。看到这你们是否是也跃跃欲试尝试着本身去转换一番代码。其实随着你们对 AST 这个方向去研究,就会发现 Vue React Babel ESlint Webpack 中的 Loader、代码对比工具中都有 AST 的影子。AST 这种对文件分析的方式其实就在咱们身边。
在写这个插件之初,我在 NPM 库中找了不少的成熟的包,这里我列举两种实现方案,仅供你们参考。
const parser = require("@babel/parser");
const remark = require("remark");
const guide = require("remark-preset-lint-md-style-guide");
const html = require("remark-html");
getAst = (path) => {
// 读取入口文件
const content = fs.readFileSync(path, "utf-8");
remark()
.use(guide)
.use(html)
.process(content, function (err, file) {
console.log(String(file));
});
};
getAst("./src/test.md");
复制代码
转换结果以下:
<h2>
mdtoVue 代码转换测试
</h2>
<pre>
<code class="language-js">
var a = 0; var b = 2;
</code>
</pre>
复制代码
使用插件 marked
。
下载
npm i marked -D
复制代码
使用
const fs = require("fs");
const marked = require("marked");
test = (path) => {
const content = fs.readFileSync(path, "utf-8");
const html = marked(content);
console.log(html);
};
test("./src/test.md");
复制代码
输出结果:
<h2 id="mdtovue-代码转换测试">mdtoVue 代码转换测试</h2>
<pre><code class="language-js">var a = 0;
var b = 2;</code></pre>
复制代码
最终我选择的是方案二 ,由于只须要 marked(content)
就完成了,至于内部是怎么处理的,咱们不用去理会。
等等还没结束,咱们的插件莫非就这么简单?固然不是了,你们喝口水慢慢看下去哈~
选定了转换工具咱们还须要去定制化其中的一些内容,例如咱们须要在里面加个二维码,加个书签目录,通常的网站都会有这类的需求,那么具体如何作到的呢?各位观众请往下看~
这里咱们拿网站中二维码展现这个功能举例:marked
暴露出了一个叫 rendererMd
的属性,咱们经过这个属性就能够处理 marked
转换以后代码的结果。
_that.rendererMd.heading = function (text, level) {
const headcode = `<i class="qrcode"><a :href="demourl"> <span>请使用手机扫码体验</span> <img :src="codeurl" alt=""></a> </i>`;
const codeHead = `<h1>` + text + headcode + `</h1>`;
if (_that.options.hasMarkList && _that.options.needCode) {
if (level == 1) {
return codeHead;
} else if (level == 2) {
return maskIdHead;
} else {
return normal;
}
}
};
复制代码
从上面的代码中咱们能够了解 rendererMd
是一个对象,其中就是 AST中的 Type ,例如:heading
、code
等等。能够经过一个 fn
,它接受两个参数 text
内容和 level
就是 depth
你们能够看看文章前面的 AST 处理结果。经过改变 marked
转换的内容,咱们能够把每一个组件文档开头二维码的 HTML 结构插入到转换结果中去 ,在把上面的转换的结果在拼接成一个 .vue
文件 就像下面这样:
write(param){
const _that = this;
return new Promise((resolve, reject) => {
const outPath = path.join(param.outsrc, param.name);
const contexts =
`<template> <div @click="dsCode"> <div v-if="content" class="layer"> <pre><span class="close-box" @click="closelayer"></span><div v-html="content"></div></pre> </div>` +
param.html +
(_that.options.hasMarkList
? '<ul class="markList">' + _that.Articlehead + "</ul>"
: "") +
`<nut-backtop :right="50" :bottom="50"></nut-backtop> </div> </template><script>import root from '../root.js'; export default { mixins:[root] }</script>`;
_that.Articlehead = "";
_that.Articleheadcount = 0;
fs.writeFile(outPath, contexts, "utf8", (err, res) => {});
});
}
复制代码
上面的整个过程咱们作了 3 件事:
mark
把 .md
文件 转换成了 HTML 语言,并插入了咱们想要定制化的代码结构。fs.writeFile
生成一个新的 .vue
文件 。这就完成了咱们 .md 转 .vue
转换的第一个功能,把 MD 语言转换成 Vue语言。
下面的内容比较枯燥无味,不过它倒是这个插件中不可或缺的部分,没有它整个转换过程将会变得奇慢无比。
有了上面的基础,接下来咱们就须要借助 Node 去进行文件的读写了,其实做为一个前端开发人员,我对于这块的掌握开始是 0 ,不过凭借着看过代码无数,心中天然有数的看片定律,经过 Node 官方文档的学习,我把 get 到的知识分享给你们,接下来献丑了。
老样子,开车以前先找路,理清思路,事半功倍!
.md
的文件。要求就是无论这个 .md
文件放在什么地方,咱们都须要它找出并解析出来。并且这个速度要快,毕竟时间就是生命。我当时首先考虑的就是一次性抓取路径并存储,再次执行的时候经过 hash 对比添加。具体思路咱们看下面的流程:
这样的好处就是只有当文件有变更的时候才会再次执行转换,若是文件没有变更咱们就没有必要去一遍遍的执行了。代码以下:
const { hashElement } = require("folder-hash");
hashElement(_that.options.entry, {
folders: { exclude: [".*", "node_modules", "test_coverage"] },
files: { include: ["*.md"] },
matchBasename: true
}).then((res) => {});
复制代码
它的返回 res
结构以下 :
{
name: ".",
hash: "YZOrKDx9LCLd8X39PoFTflXGpRU=",
children: [
{
name: "examples",
hash: "aG8wg8np5SGddTnw1ex74PC9EnM=",
children: [
{
name: "readme-example1.js",
hash: "Xlw8S2iomJWbxOJmmDBnKcauyQ8="
},
{
name: "readme-with-callbacks.js",
hash: "ybvTHLCQBvWHeKZtGYZK7+6VPUw="
},
{
name: "readme-with-promises.js",
hash: "43i9tE0kSFyJYd9J2O0nkKC+tmI="
},
{ name: "sample.js", hash: "PRTD9nsZw3l73O/w5B2FH2qniFk=" }
]
},
{ name: "index.js", hash: "kQQWXdgKuGfBf7ND3rxjThTLVNA=" },
{ name: "package.json", hash: "w7F0S11l6VefDknvmIy8jmKx+Ng=" },
{
name: "test",
hash: "H5x0JDoV7dEGxI65e8IsencDZ1A=,",
children: [
{ name: "parameters.js", hash: "3gCEobqzHGzQiHmCDe5yX8weq7M=" },
{ name: "test.js", hash: "kg7p8lbaVf1CPtWLAIvkHkdu1oo=" }
]
}
]
};
复制代码
咱们只须要一个递归把整个结构处理成一个文件路径映射就完成了 hash 的提取工做
const fileHash = {};
const disfile = (res, outpath) => {
if (res.children) {
disfile(res.children, res.name);
}
fileHash[res.name + outpath] = res.hash;
};
disfile(obj, "");
复制代码
而最终咱们获得的是一个有完整的路径和对应 hash 的对象:
{
"./src/test.md": "3gCEobqzHGzQiHmCDe5yX8weq7M",
"./src/tes2.md": "3gCEobqzHGzQiHmCDe5yX8weq7M",
"./src/test/tes2.md": "3gCEobqzHGzQiHmCDe5yX8weq7M"
}
复制代码
咱们把这个对象经过 Node 的 fs 去保存到一个 cache 文件中。可使用 fs.writeFile
把文件写进去。 这里的路径主要是方便使用 fs.readFile
去获取文件的内容进行转换。
从流程图中咱们看到有了这一步以后,再次执行的时候,咱们只要对比下文件的 hash 和历史 hash 有没有变化就好了,若是没有变化就能够跳过剩下的过程,这就节约了不少时间,提升了转换效率。 对比 hash 的代码咱们把它放到了一个新的 js 文件。
咱们在写文档的过程当中每每习惯边看边写,这里就须要有实时编译的功能。这个功能看起来很难实际上实现起来却不难:
filelisten() {
const _that = this;
const watcher = Chokidar.watch(path, {
persistent: true,
usePolling: true
});
const log = console.dir.bind(console);
const watchAction = function ({ event, eventPath }) {
// 这里进行文件更改后的操做
if (/\.md$/.test(eventPath)) {
_that.vueDesWrite(eventPath);
}
};
watcher
.on("change", (path) =>
watchAction({ event: "change", eventPath: path })
)
.on("unlink", (path) =>
watchAction({ event: "remove", eventPath: path })
);
}
复制代码
核心方法就是 Chokidar.watch
当咱们检测到有文件变更了就经过我定义的转换器把文件转换一次。
可是在写这篇文章的时候我脑洞大开,有了一个新的方案:
首先,Chokidar.watch
监听的文件越多,越会影响性能,其次,每次改变一个字符,整个文件就会从新编译一次。若是咱们可以明确 path 只监听当前编辑的文件,那么性能无疑会提高不少。
其次,就是编译转换,这里应该要使用热更新原理,相似于 Vnode 的实现方案只去更新变更的节点。目前我没有发现市面上存在现成的工具包,期待有志之士来实现这样的工具了
全部的功能都实现以后咱们须要把咱们的代码和 Webpack 融合,也就是写成 Webpack 插件的形式。那么 Webpack 的插件开发有什么要注意的呢?
其实插件的开发很是简单,只须要注意要定义一个 Apply 去用来监听 Webpack 的各类事件。
MyPlugin.prototype.apply = function(compiler) {}
复制代码
这个功能主要经过 Compiler
来实现的, Compiler
就是 Webpack 编译器的引用。经过 Compiler
能够实现对 Webpack 的监听:
Webpack 开始编译时候
apply(compiler) {
compiler.plugin("compile", function (params) {
console.log("The compiler is starting to compile...");
});
}
复制代码
Webpack 编译生成最终资源的时候
apply(compiler) {
compiler.plugin("emit", function(compilation, callback) { }
}
复制代码
其实 Webpack 在编译的过程当中还会有不少节点,咱们均可以经过这个方法去监听 Webpack 。在调用这个方法的时候还能够经过 Compilation
去对编译的对象引用监听。看到这里,很多人会搞晕 Compiler
和 Compilation
,其实它们很好区分:
Compiler
表明编译器实体,主要就是编译器上的回调事件。Compilation
表明编译过程也就是咱们在编译器中定义的进程例如:
// compilation('编译器'对'编译ing'这个事件的监听)
compiler.plugin("compilation", function(compilation) {
console.log("The compiler is starting a new compilation...");
// 在compilation事件监听中,咱们能够访问compilation引用,它是一个表明编译过程的对象引用
// 咱们必定要区分compiler和compilation,一个表明编译器实体,另外一个表明编译过程
// optimize('编译过程'对'优化文件'这个事件的监听)
compilation.plugin("optimize", function() {
console.log("The compilation is starting to optimize files...");
});
});
复制代码
咱们在下面会有详细介绍,最终在文件的结尾咱们经过
module.export = MyPlugin;
复制代码
把整个函数导出就能够了。
简单的了解完 Webpack 的插件开发,咱们还须要知道 Webpack 的处理流程,由于咱们的这个插件须要一个合适的时机进入。这里就是在 Webpack 开始执行就去处理,由于咱们转换的产物不是最终的 HTML 而是 Vue 它还须要 Webpack 去处理。
咱们但愿能够整个过程能够按照下面的流程去实现:
这样作的目的就是但愿性能更好,用起来更方便!
因此咱们须要简单的了解下 Webpack 的插件机制,这对咱们整个功能的开发有着重要的意义,当插件出现问题咱们可可以快速的定位。
经过上面这张图咱们看到, MD 转 Vue 必定要是同步执行,这里是一个关键,只有当咱们把全部的 .md
转换成 .vue
才能在让 Webpack 进行下面的工做。
而 Webpack 本质上是一种串行事件流的机制,它的工做流程就是将各个插件串联起来
实现这一切的核心就是 Tapable。
Tapable
是一个相似于 nodejs
的 EventEmitter
的库, 主要是控制钩子函数的发布与订阅。固然,Tapable
提供的 hook
机制比较全面,分为同步和异步两个大类(异步中又区分异步并行和异步串行),而根据事件执行的终止条件的不一样,由衍生出了 Bail/Waterfall/Loop 类型。
Webpack 中许多对象扩展自 Tapable
类。Tapable
类暴露了 tap、tapAsync 和 tapPromise 方法,能够根据钩子的同步/异步方式来选择一个函数注入逻辑。
compiler
对象表明了完整的 Webpack 环境配置。这个对象在启动 Webpack 时被一次性创建,并配置好全部可操做的设置,包括 options
,loader
和 plugin
。当在 Webpack 环境中应用一个插件时,插件将收到此 compiler
对象的引用。可使用 compiler
来访问 Webpack 的主环境。它内部实现大致上以下:
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
......
......
some code
};
......
......
some code
}
复制代码
能够看到, Compier
继承了 Tapable
, 而且在实例上绑定了一个 hook
对象, 使得 Compier
的实例 compier
能够像这样使用
compiler.hooks.compile.tapAsync(
"afterCompile",
(compilation, callback) => {
console.log("This is an example plugin!");
console.log(
"Here’s the `compilation` object which represents a single build of assets:",
compilation
);
// 使用 webpack 提供的 plugin API 操做构建结果
compilation.addModule(/* ... */);
callback();
}
);
复制代码
compiler
对象是 Webpack 的编译器对象,Webpack 的核心就是编译器。
compilation
对象表明了一次资源版本构建。当运行 Webpack 开发环境中间件时,每当检测到一个文件变化,就会建立一个新的 compilation
,从而生成一组新的编译资源。一个 compilation
对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation
对象也提供了不少关键时机的回调,以供插件作自定义处理时选择使用。它内部实现大致上以下:
class Compilation extends Tapable {
/** * Creates an instance of Compilation. * @param {Compiler} compiler the compiler which created the compilation */
constructor(compiler) {
super();
this.hooks = {
/** @type {SyncHook<Module>} */
buildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module>} */
rebuildModule: new SyncHook(["module"]),
/** @type {SyncHook<Module, Error>} */
failedModule: new SyncHook(["module", "error"]),
/** @type {SyncHook<Module>} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook<Dependency, string>} */
addEntry: new SyncHook(["entry", "name"]),
/** @type {SyncHook<Dependency, string, Error>} */
}
}
}
复制代码
简单来讲就是 compilation
对象负责生成编译资源。 下面咱们在介绍下 Webpack 中 compiler
和 compilation
一些比较重要的事件钩子。
Compiler:
事件钩子 | 触发时机 | 参数 | 类型 |
---|---|---|---|
entry-option | 初始化 option | - | SyncBailHook |
run | 开始编译 | compiler | AsyncSeriesHook |
compile | 真正开始的编译,在建立 compilation 对象以前 | compilation | SyncHook |
compilation | 生成好了 compilation 对象,能够操做这个对象啦 | compilation | SyncHook |
make | 从 entry 开始递归分析依赖,准备对每一个模块进行 build | compilation | AsyncParallelHook |
after-compile | 编译 build 过程结束 | compilation | AsyncSeriesHook |
emit | 在将内存中 assets 内容写到磁盘文件夹以前 | compilation | AsyncSeriesHook |
after-emit | 在将内存中 assets 内容写到磁盘文件夹以后 | compilation | AsyncSeriesHook |
done | 完成全部的编译过程 | stats | AsyncSeriesHook |
failed | 编译失败的时候 | error | SyncHook |
Compilation:
事件钩子 | 触发时机 | 参数 | 类型 |
---|---|---|---|
normal-module-loader | 普通模块 loader,真正(一个接一个地)加载模块图(graph)中全部模块的函数。 | loaderContext module | SyncHook |
seal | 编译(compilation)中止接收新模块时触发。 | - | SyncHook |
optimize | 优化阶段开始时触发。 | - | SyncHook |
optimize-modules | 模块的优化 | modules | SyncBailHook |
optimize-chunks | 优化 chunk | chunks | SyncBailHook |
additional-assets | 为编译(compilation)建立附加资源(asset)。 | - | AsyncSeriesHook |
optimize-chunk-assets | 优化全部 chunk 资源(asset)。 | chunks | AsyncSeriesHook |
optimize-assets | 优化存储在 compilation.assets 中的全部资源(asset) | assets | AsyncSeriesHook |
能够看到其实就是在 apply 中传入一个 Compiler 实例而后基于该实例注册事件, Compilation 同理, 最后 Webpack 会在各流程执行 call 方法。
其语法是
compileer.hooks.阶段.tap函数('插件名称', (阶段回调参数) => {
});
复制代码
例如:
compiler.hooks.run.tap(pluginName, compilation=>{
console.log('webpack 构建过程开始');
});
复制代码
咱们在 Node 中运行 Webpack 以后就能够看到:
$webpack ..config webpack .dev.js
webpack 构建开始
hash:f12203213123123123
.....
Done in 4.1s~~~~
复制代码
咱们也能够监听一些 Webpack 定义好的事件,以下。
compiler.plugin('complie', params => {
console.log('我是同步钩子')
});
复制代码
总结下上面的内容 Webpack 有不少事件节点,而咱们的插件经过在 apply
中就能够监听 Webpack 的过程。在适当的时机插入进去执行想要的事情。
一路走来,咱们终于把 .md
转换成了 .vue
这种组件的形式,接下来主要是 Vue 方面的开发了,终于到了前端该作的事情了。
咱们的单页面应用离不开路由,那么咱们是怎么管理的呢? 一张分布图,带你了解 NutUI 的结构
上面是咱们的主要目录,经过 MD 转 Vue 把 .md 转换的 .vue
文件所有放到 view 这个文件下。把咱们 引言等 .md
转换放到 page
里面去。其实这么作主要是为了管理员对它们的区分。
那么咱们的 router 怎么管理呢,首先咱们的项目在建立时候就会有一个 json
文件里面主要记录组件的一些信息
"sorts": [
"数据展现",
"数据录入",
"操做反馈",
"导航组件",
"布局组件",
"基础组件",
"业务组件"
],
"packages": [
{
"name": "Cell",
"version": "1.0.0",
"sort": "4",
"chnName": "列表项",
"type": "component",
"showDemo": true,
"desc": "列表项,可组合成列表",
"author": "Frans"
}
]
复制代码
接下来只要把它和咱们定好的目录结合起来就好了。
const routes = [];
list.map((item) => {
if (item.showDemo === false) return;
const pkgName = item.name.toLowerCase();
// 随着转换咱们的路径已经能够肯定了
routes.push({
path: "/" + item.name,
components: {
default: Index,
main: () => import("./view/" + pkgName + ".vue")
},
name: item.name
});
});
const router = new VueRouter({
routes
});
Vue.use(vueg, router, options);
export default router;
复制代码
咱们的网站还有全屏和复制功能,这些对于一个 Vue 项目来讲就在简单不过了,我就不在具体的描述了,只有把每一个组件的说明文档经过 mixins
把它们写在一个 js 文件中而后混入就好了。
文章到这就结束了,本文主要介绍了 MD 格式转 Vue 的实现,最终一键生成官网网页。而咱们对技术领域的探索并无结束,经过总结规划,寻找更快的解决方案,是咱们每个开发者对本身领域的执着。NutUI 在将来也会随着使用者的反馈去修改自身的不足,争取让其用户体验更加的优秀,在前端组件库丰富的时代走出一条本身的道路。前路漫漫,咱们你们一块儿去探究吧!