用vue-cli3開(kāi)發(fā)一個(gè)模仿餓了嗎的ui庫(kù)

初始化項(xiàng)目

使用vue-cli3初始化項(xiàng)目,初始化目錄如下:


image.png

將src修改為packages,用于放置組件源文件。新建example目錄用于放組件案例,獲得最新目錄:


最新目錄

配置vue.config.js

修改目錄后需要修改打包文件中對(duì)應(yīng)的文件名,在vue-cli3中需要新增vue.config.js來(lái)擴(kuò)展打包配置,參考文檔: https://cli.vuejs.org/zh/config/
配置如下:

module.exports = {
  outputDir: 'dist',    // 輸出目錄(打包后的文件夾)
  publicPath: './',
  pages: {
    index: {
      entry: 'example/main.js',     // 入口文件(開(kāi)發(fā)和生產(chǎn)中案例的入口文件) 
      template: 'public/index.html',
      filename: 'index.html'
    }
  }
}

按照以上配置執(zhí)行npm run build后會(huì)直接生成dist目錄,且將example中的案例打包,不是組件文件的打包

配置package.json

修改package.json文件:
script中新增lib命令"lib": "vue-cli-service build --target lib --name base-main --dest lib packages/index.js"

         --target:  app | lib | wc | wc-async (默認(rèn)值:app)
           --name:  打包后的組件名稱
           --dest:  指定輸出目錄 (默認(rèn)值:dist)
packages/index.js:  入口js文件

參考文檔:https://cli.vuejs.org/zh/guide/cli-service.html#vue-cli-service-build
運(yùn)行npm run lib,生成lib文件夾和組件文件base-main.umd.min.js

lib目錄

上傳組件文件到npm

  1. 配置 package.json 文件中發(fā)布到 npm 的字段
    name: 包名,該名字是唯一的。可在 npm 官網(wǎng)搜索名字,如果存在則需換個(gè)名字。
    version: 版本號(hào),每次發(fā)布至 npm 需要修改版本號(hào),不能和歷史版本號(hào)相同。
    description: 描述。
    main: 入口文件,該字段需指向我們最終編譯后的組件包文件(上面的lib文件夾中的lib/base-main.umd.min.js)。
    keyword:關(guān)鍵字,以空格分離希望用戶最終搜索的詞。
    author:作者
    private:是否私有,需要修改為 false 才能發(fā)布到 npm
    license: 開(kāi)源協(xié)議(可以填自己的github地址)
  2. 添加 .npmignore 文件,設(shè)置忽略發(fā)布文件
    發(fā)布到 npm 中,只有編譯后的 lib 目錄、package.json、README.md才是需要被發(fā)布的。所以我們需要設(shè)置忽略目錄和文件。和 .gitignore 的語(yǔ)法一樣,具體需要提交什么文件,看各自的實(shí)際情況。
  3. 登錄npm
    npm login

如果配置了淘寶鏡像,先設(shè)置回npm鏡像:
npm config set registry http://registry.npmjs.org/

  1. 發(fā)布到npm
    npm publish

  2. 更新npm版本包
    使用npm version <update_type>,對(duì)npm版本進(jìn)行更新,版本號(hào)的三位分別是大號(hào)·中號(hào)·小號(hào)·預(yù)發(fā)布號(hào)。
    update_type可以為以下值:

    1. prerelease:有預(yù)發(fā)布號(hào)的,版本號(hào)+1;無(wú)預(yù)發(fā)布號(hào)的,小號(hào)+1且預(yù)發(fā)布號(hào)初始為0
    運(yùn)行:npm version prerelease
    package.json 中的版本號(hào)1.0.0變?yōu)?1.0.1-0
    再運(yùn)行
    package.json 中的版本號(hào)1.0.1-0變?yōu)?1.0.1-1
  1. prepatch:小號(hào)+1;預(yù)發(fā)布號(hào)初始為0
   運(yùn)行:npm version prepatch
   1. package.json 中的版本號(hào)1.0.0變?yōu)?1.0.1-0
   2. package.json 中的版本號(hào)1.0.1-1變?yōu)?1.0.2-0
  1. preminor:中號(hào)+1;小號(hào)和預(yù)發(fā)布號(hào)初始為0
    運(yùn)行:npm version preminor
    1. package.json 中的版本號(hào)1.0.2-0變?yōu)?1.1.0-0
    2. package.json 中的版本號(hào)1.0.1-1變?yōu)?1.1.0-0
  1. premajor:大號(hào)+1;中號(hào),小號(hào)和預(yù)發(fā)布號(hào)初始為0
    運(yùn)行:npm version premajor
    1. package.json 中的版本號(hào)1.1.0-0變?yōu)?2.0.0-0
  1. patch:有預(yù)發(fā)布號(hào)的去掉預(yù)發(fā)布號(hào),其他不變;無(wú)預(yù)發(fā)布號(hào)的小號(hào)+1
    運(yùn)行:npm version patch
    1. package.json 中的版本號(hào)1.1.0-0變?yōu)?1.1.0
    2. package.json 中的版本號(hào)1.1.0變?yōu)?1.1.1
  1. minor:有預(yù)發(fā)布號(hào)的,小號(hào)為0時(shí)去掉預(yù)發(fā)布號(hào),其他不變,小號(hào)不為0時(shí)中號(hào)+1且其他置為0去掉預(yù)發(fā)布號(hào);無(wú)預(yù)發(fā)布號(hào)的中號(hào)+1,小號(hào)置為0
    運(yùn)行:npm version minor
    1. package.json 中的版本號(hào)1.1.0變?yōu)?1.2.0
    2. package.json 中的版本號(hào)1.1.0-0變?yōu)?1.1.0
    3. package.json 中的版本號(hào)1.1.1-0變?yōu)?1.2.0
  1. major: 無(wú)預(yù)發(fā)布號(hào)的,大號(hào)+1其他置為0;有預(yù)發(fā)布號(hào)的,中號(hào)和小號(hào)為0時(shí)去除預(yù)發(fā)布號(hào),其他不變。如果中號(hào)和小號(hào)中有一個(gè)不為0的話,大號(hào)+1,其他重置為0,去除預(yù)發(fā)布號(hào)
運(yùn)行:npm version major
    1. package.json 中的版本號(hào)1.1.0變?yōu)?2.0.0
    2. package.json 中的版本號(hào)1.0.0-0變?yōu)?1.0.0
    3. package.json 中的版本號(hào)1.1.1-0變?yōu)?2.0.0

UI文檔的編寫

這一塊參考的element-ui做法,需要新增外部包:

highlight.js    // 用于代碼的高亮
transliteration  // 用于中文拼音轉(zhuǎn)換
markdown-it
markdown-it-anchor
markdown-it-container
vue-markdown-loader

新增demo-block.vue文件

用于展示案例效果和代碼,參考element-ui的demo-block.vue文件做了修改,去除了其他語(yǔ)言,如下:

<template>
  <div
    class="demo-block"
    :class="[blockClass, { 'hover': hovering }]"
    @mouseenter="hovering = true"
    @mouseleave="hovering = false">
    <div class="source">
      <slot name="source"></slot>
    </div>
    <div class="meta" ref="meta">
      <div class="description" v-if="$slots.default">
        <slot></slot>
      </div>
      <div class="highlight">
        <slot name="highlight"></slot>
      </div>
    </div>
    <div
      class="demo-block-control"
      ref="control"
      :class="{ 'is-fixed': fixedControl }"
      @click="isExpanded = !isExpanded">
      <transition name="arrow-slide">
        <i :class="[iconClass, { 'hovering': hovering }]"></i>
      </transition>
      <transition name="text-slide">
        <span v-show="hovering">{{ controlText }}</span>
      </transition>
    </div>
  </div>
</template>

<style lang="scss">
.demo-block {
  border: solid 1px #ebebeb;
  border-radius: 3px;
  transition: .2s;

  &.hover {
    box-shadow: 0 0 8px 0 rgba(232, 237, 250, .6), 0 2px 4px 0 rgba(232, 237, 250, .5);
  }

  code {
    font-family: Menlo, Monaco, Consolas, Courier, monospace;
  }

  .demo-button {
    float: right;
  }

  .source {
    padding: 24px;
  }

  .meta {
    background-color: #fafafa;
    border-top: solid 1px #eaeefb;
    overflow: hidden;
    height: 0;
    transition: height .2s;
  }

  .description {
    padding: 20px;
    box-sizing: border-box;
    border: solid 1px #ebebeb;
    border-radius: 3px;
    font-size: 14px;
    line-height: 22px;
    color: #666;
    word-break: break-word;
    margin: 10px;
    background-color: #fff;

    p {
      margin: 0;
      line-height: 26px;
    }

    code {
      color: #5e6d82;
      background-color: #e6effb;
      margin: 0 4px;
      display: inline-block;
      padding: 1px 5px;
      font-size: 12px;
      border-radius: 3px;
      height: 18px;
      line-height: 18px;
    }
  }

  .highlight {
    pre {
      margin: 0;
    }

    code.hljs {
      margin: 0;
      border: none;
      max-height: none;
      border-radius: 0;

      &::before {
        content: none;
      }
    }
  }

  .demo-block-control {
    border-top: solid 1px #eaeefb;
    height: 44px;
    box-sizing: border-box;
    background-color: #fff;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    text-align: center;
    margin-top: -1px;
    color: #d3dce6;
    cursor: pointer;
    position: relative;

    &.is-fixed {
      position: fixed;
      bottom: 0;
      width: 868px;
    }

    i {
      font-size: 16px;
      line-height: 44px;
      transition: .3s;
      &.hovering {
        transform: translateX(-40px);
      }
    }

    > span {
      position: absolute;
      transform: translateX(-30px);
      font-size: 14px;
      line-height: 44px;
      transition: .3s;
      display: inline-block;
    }

    &:hover {
      color: #409EFF;
      background-color: #f9fafc;
    }

    & .text-slide-enter,
    & .text-slide-leave-active {
      opacity: 0;
      transform: translateX(10px);
    }

    .control-button {
      line-height: 26px;
      position: absolute;
      top: 0;
      right: 0;
      font-size: 14px;
      padding-left: 5px;
      padding-right: 25px;
    }
  }
}
</style>

<script type="text/babel">
  export default {
    data() {
      return {
        hovering: false,
        isExpanded: false,
        fixedControl: false,
        scrollParent: null,
        langConfig: {
          "hide-text": "隱藏代碼",
          "show-text": "顯示代碼"
        }
      };
    },

    props: {
      jsfiddle: Object,
      default() {
        return {};
      }
    },

    methods: {
      scrollHandler() {
        const { top, bottom, left, width } = this.$refs.meta.getBoundingClientRect();
        this.fixedControl = bottom > document.documentElement.clientHeight &&
          top + 44 <= document.documentElement.clientHeight;
        this.$refs.control.style.left = this.fixedControl ? `${ left }px` : '0';
        this.$refs.control.style.width = this.fixedControl ? `${ width }px` : 'auto';
      },

      removeScrollHandler() {
        this.scrollParent && this.scrollParent.removeEventListener('scroll', this.scrollHandler);
      }
    },

    computed: {
      lang() {
        return this.$route.path.split('/')[1];
      },

      blockClass() {
        return `demo-${ this.lang } demo-${ this.$router.currentRoute.path.split('/').pop() }`;
      },

      iconClass() {
        return this.isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom';
      },

      controlText() {
        return this.isExpanded ? this.langConfig['hide-text'] : this.langConfig['show-text'];
      },

      codeArea() {
        return this.$el.getElementsByClassName('meta')[0];
      },

      codeAreaHeight() {
        if (this.$el.getElementsByClassName('description').length > 0) {
          return this.$el.getElementsByClassName('description')[0].clientHeight +
            this.$el.getElementsByClassName('highlight')[0].clientHeight + 20;
        }
        return this.$el.getElementsByClassName('highlight')[0].clientHeight;
      }
    },

    watch: {
      isExpanded(val) {
        this.codeArea.style.height = val ? `${ this.codeAreaHeight + 1 }px` : '0';
        if (!val) {
          this.fixedControl = false;
          this.$refs.control.style.left = '0';
          this.removeScrollHandler();
          return;
        }
        setTimeout(() => {
          this.scrollParent = document.querySelector('#ex-r-area');
          this.scrollParent && this.scrollParent.addEventListener('scroll', this.scrollHandler);
          this.scrollHandler();
        }, 200);
      }
    },

    mounted() {
      this.$nextTick(() => {
        let highlight = this.$el.getElementsByClassName('highlight')[0];
        if (this.$el.getElementsByClassName('description').length === 0) {
          highlight.style.width = '100%';
          highlight.borderRight = 'none';
        }
      });
    },

    beforeDestroy() {
      this.removeScrollHandler();
    }
  };
</script>

配置vue.config.js

修改vue.config.js文件,新增chainWebpack屬性,用于文檔文件的轉(zhuǎn)換。如下:

chainWebpack: config => {
    // 設(shè)置文件夾別名
    config.resolve.alias
      .set('@', resolve('example'))
      .set('~', resolve('packages'))
    config.module
      .rule('js')
      .include
        .add(__dirname + 'packages')
        .end()
      .use('babel')
        .loader('babel-loader')
        .tap(options => {
          // 修改它的選項(xiàng)...
          return options
        })
    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,
        preventExtract: true, //這個(gè)加載器將自動(dòng)從html令牌內(nèi)容中提取腳本和樣式標(biāo)簽
        // 定義處理規(guī)則
        preprocess: (MarkdownIt, source) => {
          // 對(duì)于markdown中的table,
          MarkdownIt.renderer.rules.table_open = function() {
            return '<table class="doctable">';
          };
          // 對(duì)于代碼塊去除v - pre, 添加高亮樣式;
          const defaultRender = md.renderer.rules.fence;
          MarkdownIt.renderer.rules.fence = (
            tokens,
            idx,
            options,
            env,
            self
          ) => {
            const token = tokens[idx];
            // 判斷該 fence 是否在 :::demo 內(nèi)
            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: [
          // 標(biāo)題錨點(diǎn)
          [
            require("markdown-it-anchor"),
            {
              level: 2, // 添加超鏈接錨點(diǎn)的最小標(biāo)題級(jí)別, 如: #標(biāo)題 不會(huì)添加錨點(diǎn)
              slugify: slugify, // 自定義slugify, 我們使用的是將中文轉(zhuǎn)為漢語(yǔ)拼音,最終生成為標(biāo)題id屬性
              permalink: true, // 開(kāi)啟標(biāo)題錨點(diǎn)功能
              permalinkBefore: true // 在標(biāo)題前創(chuàng)建錨點(diǎn)
            }
          ],
          // :::demo ****
          //
          // :::
          //匹配:::后面的內(nèi)容 nesting == 1,說(shuō)明:::demo 后面有內(nèi)容
          //m為數(shù)組,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] : ""; // 獲取正則捕獲組中的描述內(nèi)容,即::: 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"]
        ]
      });
  }

設(shè)置文檔文件

在example中新增doc文件夾用于存放md文件,在md文件中使用markdown語(yǔ)法書寫文檔。其中以:::demo開(kāi)始對(duì)組件進(jìn)行使用的講解,在demo后面可以填寫組件的中相關(guān)的屬性和方法的使用,在```html 代碼塊 ```的代碼塊中填寫組件案例的使用代碼,和函數(shù)方法。案例:

:::demo 使用`type`、`plain`、`round`和`circle`屬性來(lái)定義 Button 的樣式。

 ```html
<div class="mb-20">
  <el-button>默認(rèn)按鈕</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">危險(xiǎn)按鈕</el-button>
</div>
<script lang="babel">
export default{
}
</script>
``` // html的結(jié)尾符
:::

修改example中的路由文件,設(shè)置對(duì)應(yīng)案例的對(duì)應(yīng)的路由,如下:

new Router({
  routes: [
    {
      path: '/ElButton',
      name: 'ElButton',
      text: 'button按鈕',
      component: () => import(`@/doc/ElButton.md`)
    }
  ]
})

預(yù)覽地址:https://erpang123.github.io/C-UI/CUI/index.html
參考的相關(guān)文章:https://blog.csdn.net/qq_31126175/article/details/100527322
https://segmentfault.com/a/1190000021140844

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容