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

初始化項目

使用vue-cli3初始化項目蹦浦,初始化目錄如下:


image.png

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


最新目錄

配置vue.config.js

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

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

按照以上配置執(zhí)行npm run build后會直接生成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 (默認值:app)
           --name:  打包后的組件名稱
           --dest:  指定輸出目錄 (默認值:dist)
packages/index.js:  入口js文件

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

lib目錄

上傳組件文件到npm

  1. 配置 package.json 文件中發(fā)布到 npm 的字段
    name: 包名瓮恭,該名字是唯一的±迨欤可在 npm 官網(wǎng)搜索名字屯蹦,如果存在則需換個名字。
    version: 版本號绳姨,每次發(fā)布至 npm 需要修改版本號登澜,不能和歷史版本號相同。
    description: 描述就缆。
    main: 入口文件帖渠,該字段需指向我們最終編譯后的組件包文件(上面的lib文件夾中的lib/base-main.umd.min.js)。
    keyword:關(guān)鍵字竭宰,以空格分離希望用戶最終搜索的詞空郊。
    author:作者
    private:是否私有份招,需要修改為 false 才能發(fā)布到 npm
    license: 開源協(xié)議(可以填自己的github地址)
  2. 添加 .npmignore 文件,設(shè)置忽略發(fā)布文件
    發(fā)布到 npm 中狞甚,只有編譯后的 lib 目錄锁摔、package.json、README.md才是需要被發(fā)布的哼审。所以我們需要設(shè)置忽略目錄和文件谐腰。和 .gitignore 的語法一樣,具體需要提交什么文件涩盾,看各自的實際情況十气。
  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>春霍,對npm版本進行更新砸西,版本號的三位分別是大號·中號·小號·預(yù)發(fā)布號
    update_type可以為以下值:

    1. prerelease:有預(yù)發(fā)布號的址儒,版本號+1芹枷;無預(yù)發(fā)布號的,小號+1且預(yù)發(fā)布號初始為0
    運行:npm version prerelease
    package.json 中的版本號1.0.0變?yōu)?1.0.1-0
    再運行
    package.json 中的版本號1.0.1-0變?yōu)?1.0.1-1
  1. prepatch:小號+1莲趣;預(yù)發(fā)布號初始為0
   運行:npm version prepatch
   1. package.json 中的版本號1.0.0變?yōu)?1.0.1-0
   2. package.json 中的版本號1.0.1-1變?yōu)?1.0.2-0
  1. preminor:中號+1鸳慈;小號和預(yù)發(fā)布號初始為0
    運行:npm version preminor
    1. package.json 中的版本號1.0.2-0變?yōu)?1.1.0-0
    2. package.json 中的版本號1.0.1-1變?yōu)?1.1.0-0
  1. premajor:大號+1;中號喧伞,小號和預(yù)發(fā)布號初始為0
    運行:npm version premajor
    1. package.json 中的版本號1.1.0-0變?yōu)?2.0.0-0
  1. patch:有預(yù)發(fā)布號的去掉預(yù)發(fā)布號走芋,其他不變;無預(yù)發(fā)布號的小號+1
    運行:npm version patch
    1. package.json 中的版本號1.1.0-0變?yōu)?1.1.0
    2. package.json 中的版本號1.1.0變?yōu)?1.1.1
  1. minor:有預(yù)發(fā)布號的絮识,小號為0時去掉預(yù)發(fā)布號绿聘,其他不變嗽上,小號不為0時中號+1且其他置為0去掉預(yù)發(fā)布號次舌;無預(yù)發(fā)布號的中號+1,小號置為0
    運行:npm version minor
    1. package.json 中的版本號1.1.0變?yōu)?1.2.0
    2. package.json 中的版本號1.1.0-0變?yōu)?1.1.0
    3. package.json 中的版本號1.1.1-0變?yōu)?1.2.0
  1. major: 無預(yù)發(fā)布號的兽愤,大號+1其他置為0彼念;有預(yù)發(fā)布號的,中號和小號為0時去除預(yù)發(fā)布號浅萧,其他不變逐沙。如果中號和小號中有一個不為0的話,大號+1洼畅,其他重置為0吩案,去除預(yù)發(fā)布號
運行:npm version major
    1. package.json 中的版本號1.1.0變?yōu)?2.0.0
    2. package.json 中的版本號1.0.0-0變?yōu)?1.0.0
    3. package.json 中的版本號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文件做了修改徘郭,去除了其他語言靠益,如下:

<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 => {
          // 修改它的選項...
          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, //這個加載器將自動從html令牌內(nèi)容中提取腳本和樣式標簽
        // 定義處理規(guī)則
        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 內(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: [
          // 標題錨點
          [
            require("markdown-it-anchor"),
            {
              level: 2, // 添加超鏈接錨點的最小標題級別, 如: #標題 不會添加錨點
              slugify: slugify, // 自定義slugify, 我們使用的是將中文轉(zhuǎn)為漢語拼音,最終生成為標題id屬性
              permalink: true, // 開啟標題錨點功能
              permalinkBefore: true // 在標題前創(chuàng)建錨點
            }
          ],
          // :::demo ****
          //
          // :::
          //匹配:::后面的內(nèi)容 nesting == 1,說明:::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語法書寫文檔抱环。其中以:::demo開始對組件進行使用的講解壳快,在demo后面可以填寫組件的中相關(guān)的屬性和方法的使用,在```html 代碼塊 ```的代碼塊中填寫組件案例的使用代碼镇草,和函數(shù)方法眶痰。案例:

:::demo 使用`type`、`plain`梯啤、`round`和`circle`屬性來定義 Button 的樣式凛驮。

 ```html
<div class="mb-20">
  <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>
</div>
<script lang="babel">
export default{
}
</script>
``` // html的結(jié)尾符
:::

修改example中的路由文件,設(shè)置對應(yīng)案例的對應(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)容合作請聯(lián)系作者
  • 序言:七十年代末黔夭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子羽嫡,更是在濱河造成了極大的恐慌本姥,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杭棵,死亡現(xiàn)場離奇詭異婚惫,居然都是意外死亡,警方通過查閱死者的電腦和手機魂爪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門先舷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滓侍,你說我怎么就攤上這事蒋川。” “怎么了撩笆?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵捺球,是天一觀的道長。 經(jīng)常有香客問我夕冲,道長氮兵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任歹鱼,我火速辦了婚禮泣栈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己南片,他們只是感情好篙悯,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铃绒,像睡著了一般鸽照。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颠悬,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天矮燎,我揣著相機與錄音,去河邊找鬼赔癌。 笑死诞外,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的灾票。 我是一名探鬼主播峡谊,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼刊苍!你這毒婦竟也來了既们?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤正什,失蹤者是張志新(化名)和其女友劉穎啥纸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體婴氮,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡斯棒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了主经。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片荣暮。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖罩驻,靈堂內(nèi)的尸體忽然破棺而出穗酥,到底是詐尸還是另有隱情,我是刑警寧澤鉴腻,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布迷扇,位于F島的核電站百揭,受9級特大地震影響爽哎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜器一,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一课锌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦渺贤、人聲如沸雏胃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞭亮。三九已至,卻和暖如春固棚,著一層夾襖步出監(jiān)牢的瞬間统翩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工此洲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厂汗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓呜师,卻偏偏與公主長得像娶桦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汁汗,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359