文章目錄
用vue-cli3初始化項目
css 選了sass
lint 選了ESLint + Prettier
vscode 可下載eslint插件,配置保存自動格式化代碼
個人配置如下(首選項–>設置)
{
"eslint.autoFixOnSave": true,
"eslint.options": {
"extensions": [
".js",
".vue"
]
},
"eslint.validate": [
"javascript",
{
"language": "vue",
"autoFix": true
},
"html",
"vue"
],
}
修改項目結構
- 把 src 目錄名字改成
examples
,這是用於展示組件示例的 - 在根目錄下新建一個
packages
文件夾,這是用來放組件的 - 在根目錄下新建一個 vue.config.js 文件
const path = require('path')
module.exports = {
// 修改 pages 入口
pages: {
index: {
entry: 'examples/main.js', // 入口
template: 'public/index.html', // 模板
filename: 'index.html' // 輸出文件
}
},
// 擴展 webpack 配置
chainWebpack: config => {
// @ 默認指向 src 目錄,這裏要改成 examples
// 另外也可以新增一個 ~ 指向 packages
config.resolve.alias
.set('@', path.resolve('examples'))
.set('~', path.resolve('packages'))
// 把 packages 和 examples 加入編譯,因爲新增的文件默認是不被 webpack 處理的
config.module
.rule('js')
.include.add(/packages/).end()
.include.add(/examples/).end()
.use('babel')
.loader('babel-loader')
.tap(options => {
// 修改它的選項...
return options
})
}
}
先寫個測試demo
1. packages下新建文件夾test,結構如下:
packages
└── test
├── index.js
└── src
└── test.vue
test.vue 組件內容
<template>
<div>
<button @click="add">{{ num }}</button>
</div>
</template>
<script>
export default {
name: "YuanTest", // 這個名字很重要,它就是未來的標籤名<yuan-test></yuan-test>
data() {
return {
num: 1
};
},
methods: {
add() {
this.num++;
}
}
};
</script>
<style></style>
test/index.js 暴露組件 針對單個組件的安裝,因爲 Vue.use() 會默認調用 install 方法安裝.
// 爲組件提供 install 方法,供組件對外按需引入
import YuanTest from "./src/test";
YuanTest.install = Vue => {
Vue.component(YuanTest.name, YuanTest);
};
export default YuanTest;
全局安裝 ,在packages的根目錄下,創建index,js,用於循環安裝所有組件
import YuanTest from "./test";
// 所有組件列表
const components = [YuanTest];
// 定義 install 方法,接收 Vue 作爲參數
const install = function(Vue) {
// 判斷是否安裝,安裝過就不繼續往下執行
if (install.installed) return;
install.installed = true;
// 遍歷註冊所有組件
components.map(component => Vue.component(component.name, component));
// 下面這個寫法也可以
// components.map(component => Vue.use(component))
};
// 檢測到 Vue 才執行,畢竟我們是基於 Vue 的
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
export default {
install,
// 所有組件,必須具有 install,才能使用 Vue.use()
...components
};
2. examples進行測試
examples/main.js
添加以下內容
//導入組件
import YuanUI from "../packages/index";
//註冊組件
Vue.use(YuanUI);
examples/views/Home.vue
調用組件
<template>
<div>
<yuan-test></yuan-test>
</div>
</template>
<script>
export default {};
</script>
<style></style>
3. examples展示markdown文件
element-ui的示例文件都是markdown的.
npm i vue-markdown-loader -D
npm i vue-loader vue-template-compiler -D
或者
yarn add vue-markdown-loader vue-loader vue-template-compiler -D
高亮樣式
npm i highlight.js -D 或者 yarn add highlight.js
- vue.config.js 添加
module.exports = {
...
chainWebpack: config => {
...
config.module.rule('md')
.test(/\.md/)
.use('vue-loader')
.loader('vue-loader')
.end()
.use('vue-markdown-loader')
.loader('vue-markdown-loader/lib/markdown-compiler')
.options({
raw: true
})
}
}
- 測試使用 在home.vue中,寫如下代碼,並在同級新建
test.md
文件,寫入## test
文本
<template>
<div>
<test></test>
</div>
</template>
<script>
import test from "./test.md";
import "highlight.js/styles/github.css";
export default {
components: {
test
}
};
</script>
<style></style>
現在基本能用了,我們調整下展示頁面的佈局
examples
├── App.vue
├── assets
│ └── logo.png
├── components 頁面佈局的組件
├── docs 文檔
│ └── test.md
├── main.js
├── router.js
├── store.js
└── store.js
爲方便展示,將頁面佈局樣式全放在了app.vue中,可根據個人需要,自行拆分成組件放在components,並在app.vue中引用.
App.vue
<template>
<div id="app">
<!-- header -->
<div class="header"></div>
<div class="main">
<!-- sidebar -->
<div class="sidebar">
<router-link to="test">test</router-link>
</div>
<div class="view">
<router-view></router-view>
</div>
</div>
<!-- footer -->
<div class="footer"></div>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss">
html,
body {
margin: 0;
}
.header,
.footer {
height: 60px;
background-color: antiquewhite;
}
.main {
min-height: calc(100vh - 120px);
display: flex;
}
.sidebar {
width: 200px;
}
.view {
flex: 1;
}
</style>
router.js
import Vue from "vue";
import Router from "vue-router";
Vue.use(Router);
export default new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "test",
component: () => import("@/docs/test.md")
//component: r => require.ensure([], () => r(require(`@/docs/test.md`)))
}
]
});
4. 像elementui 一樣的 可操作展示
markdown-it 渲染 markdown 基本語法
markdown-it-anchor 爲各級標題添加錨點
markdown-it-container 用於創建自定義的塊級容器
vue-markdown-loader 核心loader
transliteration 中文轉拼音
highlight.js 代碼塊高亮實現
vue.config.js
const path = require("path");
const md = require("markdown-it")(); // 引入markdown-it
const slugify = require("transliteration").slugify; // 引入transliteration中的slugify方法
module.exports = {
// 修改 pages 入口
pages: {
index: {
entry: "examples/main.js", // 入口
template: "public/index.html", // 模板
filename: "index.html" // 輸出文件
}
},
parallel: false, //解決打包時含script標籤,報錯問題 https://github.com/QingWei-Li/vue-markdown-loader/issues/61
// 擴展 webpack 配置
chainWebpack: config => {
// @ 默認指向 src 目錄,這裏要改成 examples
// 另外也可以新增一個 ~ 指向 packages
config.resolve.alias
.set("@", path.resolve("examples"))
.set("~", path.resolve("packages"));
// 把 packages 和 examples 加入編譯,因爲新增的文件默認是不被 webpack 處理的
config.module
.rule("js")
.include.add(/packages/)
.end()
.include.add(/examples/)
.end()
.use("babel")
.loader("babel-loader")
.tap(options => {
// 修改它的選項...
return options;
});
//markdown
config.module
.rule("md")
.test(/\.md/)
.use("vue-loader")
.loader("vue-loader")
.end()
.use("vue-markdown-loader")
.loader("vue-markdown-loader/lib/markdown-compiler")
// .loader(path.resolve(__dirname, "./md-loader/index.js"));//element-ui的md處理在md-loader中,這裏沒有使用.處理方式在下面
.options({
raw: true,
preventExtract: true, //這個加載器將自動從html令牌內容中提取腳本和樣式標籤
// 定義處理規則
preprocess: (MarkdownIt, source) => {
// 對於markdown中的table,
MarkdownIt.renderer.rules.table_open = function() {
return '<table class="doctable">';
};
// 對於代碼塊去除v - pre, 添加高亮樣式;
const defaultRender = md.renderer.rules.fence;
MarkdownIt.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);
};
return source;
},
use: [
// 標題錨點
[
require("markdown-it-anchor"),
{
level: 2, // 添加超鏈接錨點的最小標題級別, 如: #標題 不會添加錨點
slugify: slugify, // 自定義slugify, 我們使用的是將中文轉爲漢語拼音,最終生成爲標題id屬性
permalink: true, // 開啓標題錨點功能
permalinkBefore: true // 在標題前創建錨點
}
],
// :::demo ****
//
// :::
//匹配:::後面的內容 nesting == 1,說明:::demo 後面有內容
//m爲數組,m[1]表示 ****
[
require("markdown-it-container"),
"demo",
{
validate: function(params) {
return params.trim().match(/^demo\s*(.*)$/);
},
render: function(tokens, idx) {
const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/);
if (tokens[idx].nesting === 1) {
//
const description = m && m.length > 1 ? m[1] : ""; // 獲取正則捕獲組中的描述內容,即::: demo xxx中的xxx
const content =
tokens[idx + 1].type === "fence"
? tokens[idx + 1].content
: "";
return `<demo-block>
<div slot="source">${content}</div>
${description ? `<div>${md.render(description)}</div>` : ""}
`;
}
return "</demo-block>";
}
}
],
[require("markdown-it-container"), "tip"],
[require("markdown-it-container"), "warning"]
]
});
}
};
demoblock.vue代碼省略,地址查看
高亮 main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import hljs from "highlight.js";
//導入組件
import YuanUI from "../packages/index";
import DemoBlock from "./components/DemoBlock.vue";
import "~/theme-chalk/src/index.scss"; //組件樣式
import "./assets/styles/common.scss"; //公共樣式
import "./demo-styles/index.scss"; //文檔 展示樣式
Vue.component("DemoBlock", DemoBlock);
router.afterEach(route => {
Vue.nextTick(() => {
const blocks = document.querySelectorAll("pre code:not(.hljs)");
Array.prototype.forEach.call(blocks, hljs.highlightBlock);
});
});
//註冊組件
Vue.use(YuanUI);
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
test.md
## tip
:::tip
需要注意的是,覆蓋字體路徑變量是必需的,將其賦值爲 Element 中 icon 圖標所在的相對路徑即可。
:::
## warning
:::warning
Input 爲受控組件,它**總會顯示 Vue 綁定值**。
通常情況下,應當處理 `input` 事件,並更新組件的綁定值(或使用`v-model`)。否則,輸入框內顯示的值將不會改變。
不支持 `v-model` 修飾符。
:::
## demo
:::demo
```html
<yuan-test></yuan-test>
```
:::