用vue-cli3搭建ui庫(仿elementUI)

用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"
    ],
}

修改項目結構

  1. 把 src 目錄名字改成 examples,這是用於展示組件示例的
  2. 在根目錄下新建一個 packages 文件夾,這是用來放組件的
  3. 在根目錄下新建一個 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的.

  1. 安裝 vue-markdown-loader
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
  1. 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
      })
  }
}
  1. 測試使用 在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>
	```
	:::

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章