从0到1实现一个Vue组件库(入门篇)

8,060 阅读8分钟

什么是vue组件库

Vue:一套用于构建用户界面的渐进式框架

Vue组件:组件是可复用的Vue实例 -*.Vue

Vue组件库:基于Vue实现的,独立于业务的第三方UI库,如Element、Ant Design

开始

一、项目初始化

1.用脚手架生成一个项目

安装地址:cli.vuejs.org/zh/guide/in…

npm install -g @vue/cli
vue create m-ui

这里选用vue2进行开发

2.修正项目结构

vue脚手架生成的目录如下:

我们需要进行一些修改:

src重命名为examples,并添加UI目录,用来存放组件相关逻辑代码,脚手架默认会启动src下的服务,因为目录名变了,所以我们需要在vue.config.js下重新配置项目入口。

修改后的目录结构:

3.跑通一个Button组件

先用Button组件做个示范,首先需要在UI目录下新建一个packages目录用来存放我们的自定义组件,同时新建一个css目录用来存放组件的样式代码,然后在packages目录下新建一个button目录,在其目录下新建src目录,用来存放组件的源代码main.vue

准备工作做好后就可以来封装我们的Button组件了。

1.在main.vue中编写button组件逻辑代码

这里我使用scss来处理css代码:安装依赖npm i sass sass-loade@5 node-sass -D(这里组件的样式已省略,可以自行设计)

<template>
  <button class="m-button" v-bind:class="[`m-button--${type}`,`m-button--${size}`,
  {'is-plain':plain},
  {'is-round':round},
  {'is-circle':circle},
  {'is-disabled':disabled},
  {'is-loading':loading}]"
  @click="handleClick"
          :disabled="disabled || loading"
          :loading="loading"
  >
    <i class="m-icon-loading" v-if="loading"></i>
    <i  v-if="icon&&!loading" :class="`iconfont icon-${icon} `"></i>
    <!--如果没有传入文本插槽,则不显示span内容-->
    <span v-if="$slots.default">
        <slot></slot>
    </span>
  </button>
</template>

<script>
export default {
  name: "MButton",
  props:{
    size:{
      type:String,
      default:'14px',
    },
    type:{
      type:String,
      default:'default',
    },
    plain:{
      type:Boolean,
      default:false
    },
    round:{
      type:Boolean,
      default:false,
    },
    circle:{
      type:Boolean,
      default:false,
    },
    loading:{
      type:Boolean,
      default:false,
    },
    disabled:{
      type:Boolean,
      default:false,
    },
    icon:{
      type:String,
      default:"",
    },
    autofocus:{
      type:Boolean,
      default:false,
    }
  },
  methods: {
    handleClick(evt) {
      this.$emit('click', evt);
    }
  }
}
</script>

2.在button目录下创建index.js文件,用来作为组件对外的接口(实现按需引入)

import Button from './src/main.vue'
Button.install = function(Vue) {
    Vue.component(Button.name, Button)
}
export default Button

3.在examples下的main.js中添加Button组件

为什么使用组件库的时候在main.js下直接用 Vue.use(Button) 就可以使用组件了呢?

因为Vue.use(Button)会默认调用Button的Button.install()方法再去执行里面的Vue.component()去注册组件。

4.在App.vue中引入Button组件

<div id="app">
  <img alt="Vue logo" src="./assets/logo.png">
  <M-Button  type="main"  size="small" icon="correct">封装成功</M-Button>
</div>

这样在页面上就能呈现一个Button组件了

image.png

二、设计一个badge组件

1.怎么设计一个组件?

由外到内分析,从外看整体需要什么属性,然后划分为每块区域需要什么属性和样式,把需要的api整理成一个表格

组件的主要用途:复用=>通用

了解大概的功能后,可以开始动手操作了,前面的步骤和Button组件是一样的,所以不再赘述。这里放上目录

2.编写组件逻辑代码

main.vue

<template>
  <div class="m-badge">
      <slot></slot>
      <sup
        v-show="!hidden && (content || content === 0 || isDot)"
        v-text="content"
        class="m-badge__content"
        :class="[
          'm-badge__content--' + type,
          {
            'is-fixed': $slots.default,
            'is-dot': isDot
          }
        ]">
      </sup>
  </div>
</template>

<script>
export default {
    name:'MBadge',
    props: {
    value: [String, Number],
    max: Number,
    isDot: Boolean,
    hidden: Boolean,
    type: {
      type: String,
      validator(val) {
         // 这个值必须匹配下列字符串中的一个
        return ['primary', 'success', 'warning', 'info', 'danger'].indexOf(val) > -1;
      }
    }
  },

  computed: {
    content() {
      if (this.isDot) return;
      const value = this.value;
      const max = this.max;
      if (typeof value === 'number' && typeof max === 'number') {
        return max < value ? `${max}+` : value;
      }
      return value;
    }
  }
}
</script>

badge.scss

.m-badge {
  position: relative;
  vertical-align: middle;
  display: inline-block;
  &__content {
    background-color: #f06897;
    border-radius: 10px;
    color: #FFF;
    display: inline-block;
    font-size: 12px;
    height: 18px;
    line-height: 18px;
    padding: 0 6px;
    text-align: center;
    white-space: nowrap;
    border: 1px solid #FFF
  }
  &__content.is-fixed {
    position: absolute;
    top: 0;
    right: 10px;
    -webkit-transform: translateY(-50%) translateX(100%);
    transform: translateY(-50%) translateX(100%)
  }
  &__content.is-fixed.is-dot {
    right: 5px
  }
  &__content.is-dot {
    height: 8px;
    width: 8px;
    padding: 0;
    right: 0;
    border-radius: 50%
  }
  &__content--primary {
    background-color: #0ba5f8;
  }
  &__content--success {
    background-color: #00ce3f;
  }
  &__content--warning {
    background-color: #e79f18;
  }
  &__content--info {
    background-color: #a8ddfa;
  }
  &__content--danger {
    background-color: #ed437d;
  }
}

index.js

import Badge from './src/main.vue'
Badge.install = function(Vue) {
  Vue.component(Badge.name, Badge);
};

export default Badge;

3.测试组件

1.在examples下的mian.js中添加Badge组件

2.在App.vue页面中使用组件:

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png">
    <M-Button  type="main"  size="small" icon="correct">封装成功</M-Button>
    <div style="width:500px; display:flex;justify-content: space-between;">
      <M-Badge :value="1" type="success">
        <M-Button size="small">Number类型</M-Button>
      </M-Badge>
      <M-Badge :value="100" :max="99" type="warning">
        <M-Button size="small">限制最大值</M-Button>
      </M-Badge>
      <M-Badge is-dot>
        <M-Button size="small" icon="look">小圆点</M-Button>
      </M-Badge>
      <M-Badge value="hot" type="primary">
        <M-Button size="small" >String类型</M-Button>
      </M-Badge>   
    </div>
  </div>
</template>

3.效果展示:

三、组件库打包

使用webpack打包js为umd模块

现在组件只能实现按需引入,因为并没有配置整个组件库的入口,所以需要在packages下面新建一个index.js文件来配置一个组件库的入口

准备工作完成后,我们开始配置webpack:

1.根目录下创建一个webpack配置文件(名字任意):webpack.component.js

2.安装环境依赖:

  • 安装webpack-cli:npm i webpack-cli
  • 为了处理.vue结尾的文件,安装vue-loader:npm i vue-loader@15 -D

3.编写webpack打包逻辑

const path = require('path')
const { VueLoaderPlugin } = require('vue-loader')
const glob = require('glob'); //node自带的模块用于遍历
const list = {};
async function makeList(dirPath, list) {
    const files = glob.sync(`${dirPath}/**/index.js`); //console.log(files):'UI/packages/button/index.js'.'...'
    for (let file of files) {
        const component = file.split(/[/.]/)[2]; //取到组件name
        list[component] = `./${file}`; //添加到list对象
    }
}
makeList('UI/packages', list);
module.exports = {
    entry: list,
    mode: 'development',
    output: {
        filename: '[name].umd.js',
        path: path.resolve(__dirname, 'dist'),
        library: 'mui',
        libraryTarget: 'umd'
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    module: {
        rules: [{
            test: /.vue$/,
            use: [{
                loader: 'vue-loader',
            }]
        }]
    }
}

4.配置打包命令:找到package.json文件,配置build:js打包命令

5.执行npm run build:js命令进行打包

打包后生成dist目录 其中的**.umd.js就是我们打包好的文件

到此为止,已经完成了组件库js部分的打包了!

gulp打包css

gulp官网:www.gulpjs.com.cn/

在css目录下新建一个index.scss来收归所有css代码:

@import './button.scss';
@import './badge.scss';

1.安装打包需要的依赖:npm i sass gulp gulp-sass gulp-minify-css -D

2.在项目根目录下新建一个文件gulpfile.js来配置打包代码

const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'))
const minifyCSS = require('gulp-minify-css');
gulp.task('sass', async function() {
    return gulp.src('UI/css/**/*.scss')
        .pipe(sass())
        .pipe(minifyCSS())
        .pipe(gulp.dest('dist/css'))
});

3.在package.jsonscripts下 配置css打包命令:"build:css":"npx gulp sass"

4.执行打包命令:build:css

可以得到打包后的css

5.合并打包命令:

这样只要执行一次build命令就能同时打包jscss文件了。

为什么用webpack和gulp两个打包工具?

1.Gulp侧重于前端开发的整个过程的控制管理(像是流水线),我们可以通过给gulp配置不同的task(通过Gulp中的gulp.task()方法配置,比如启动server、sass/less预编译、文件的合并压缩等等)来让gulp实现不同的功能,从而构建整个前端开发流程。

2.Webpack有人也称之为 模块打包机 ,由此也可以看出Webpack更侧重于模块打包,当然我们可以把开发中的所有资源(图片、js文件、css文件等)都可以看成模块,最初Webpack本身就是为前端JS代码打包而设计的,后来被扩展到其他资源的打包处理。Webpack是通过loader(加载器)和plugins(插件)对资源进行处理的。

3.另外我们知道Gulp是对整个过程进行控制,所以在其配置文件(gulpfile.js)中配置的每一个task对项目中该task配置路径下所有的资源都可以管理。比如,对sass文件进行预编译的task可以对其配置路径下的所有sass文件进行预编译处理

四、组件库发布

1.发布到npm

1.先来修改一下package.json配置,首先删除private

在删除的地方添加组件库的配置信息

    "name": "m-ui0",
    "version": "0.1.0",
    //项目文件中不能添加注释,此举是方便解释
    "description": "组件库教程",//表述信息
    "main": "dist/index.umd.js",//组件库的入口文件
    "keywords": [//关键词 便于查找到我们的组件库
        "m-ui0",
        "vue",
        "ui"
    ],
    "author": "moon",//作者信息
    //不需要把所有目录都发布到包里,可以指定希望发布的文件目录
    "files": [
        "dist",
        "UI"//为了方便用户查看源码
    ],

3.更新README.md文件,这里主要是用来介绍我们组件库的一些使用和说明。

image.png

以上全部做完,就可以开始发布我们的组件库了

登录npm官网:www.npmjs.com/

1.先来注册一个npm账号:

注意:在运行npm login命令之前。必须先把下包的服务器地址切换为npm的官方服务器。否则会导致发布包失败!

2.可以执行 nrm ls命令 检测登录的服务器是否为官方服务器

关于nrm工具的用法这里不再赘述,可以参考这篇文章:blog.csdn.net/jcodery/art…

3.检查完后在终端执行npm login命令,依次输入用户名、密码、邮箱后,即可登录成功。

4.登录成功后执行npm publish命令 发布我们的组件库

4.大功告成!现在就可以在npm官方上看到我们的组件库了:

点击头像=>Packages

点进去可以看到详细信息:

2.测试发布后的组件库

1.新建一个项目

按照README提示

2.先安装我们的组件库npm i m-ui0

3.在main.js中引入我们的组件库

//全部引入
import 'm-ui0/dist/css/index.css';
import MUI from 'm-ui0';
Vue.use(MUI)

4.在App.vue中使用组件

5.重新启动项目看效果

五、组件库文档搭建

1.vuepress

中文文档:www.vuepress.cn/guide/getti…

1.将 VuePress 安装为本地依赖:执行npm install -D vuepress

2.创建第一篇文档:执行mkdir docs ; echo '# Hello VuePress' > docs/README.md

运行成功后会生成一个docs目录,其中的README.md文件就是我们的文档

3.在 package.json 中添加一些 scripts

{
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}

4.在本地启动服务器:执行npm run docs:dev

运行成功后就可以看到我们的文档了

乱码问题解决方法:

方法一:①找到README.md文件,用记事本打开 ②选择 文件->另存为,编码改为UTF-8->保存 ③然后就得到正常的页面了。

方法二:删除原README.md文件,重新创建。

2.初始化组件库文档结构

1.添加一个侧边栏

侧边栏需要两个内容:主页和文档

这里用Button组件来进行演示

⑴创建卡片组件对应的入口,在docs目录下新建componentDocs文件夹在其中新建button.md文件:用来编写Button组件的文档

⑵在docs目录下新建一个.vuepress文件夹并在其中新建一个config.js配置文件

想要使 侧边栏(Sidebar)生效,需要配置 themeConfig.sidebar,基本的配置,需要一个包含了多个链接的数组,配置如下:

.vuepress/config.js

module.exports = {
  themeConfig: {
    sidebar: [
      '/',
      '/componentsDocs/button'
    ]
  }
}

配置完成后重新启动文档,会出现如下页面:

2.在md文件中使用vue组件

引用官方文档的示例:

所有在 .vuepress/components 中找到的 *.vue 文件将会自动地被注册为全局的异步组件,如:

你可以直接使用这些组件在任意的 Markdown 文件中(组件名是通过文件名取到的):

<demo-1/>
<OtherComponent/>
<Foo-Bar/>

下面我们来正式操作:

⑴在.vuepress目录下新建一个components文件夹

⑵把UI下的Button组件main.vue以及其样式scss文件复制一份到components目录下,并进行如下修改:

①将main.vue文件改名为M-Button.vue,因为后续还会有其他组件被引用,便于区分;

②在main.vue中引用scss文件

⑶之后就可以在md文件中直接使用我们的组件了

注意:md中使用的组件名和我们起的文件名要保持一致

页面效果:

3.编写组件文档

1.先来编写button组件文档:

来到button.md文件,编写说明文档:

页面效果:

2.编写主页文档

来到README.md文件,编写说明文档:

页面效果:

4.在github上创建个人站点

我们需要创建一个GitHub Pages站点

详情可参考GitHub文档:docs.github.com/cn/pages/ge…

首先登录githup网站:github.com/ 新建一个仓库

我们仓库名称必须是<user>.github.io格式:

创建成功后,可以在Settings下的Pages中查看我们的站点

目前站点没有任何配置,它默认先去找index 文件,如果找不到会显示README.md文件的内容

5.将文档站点部署到github.io

1.首先我们再新建一个仓库,名字随意

2.仓库建好后,让其和我们本地的项目绑定,并推送到线上仓库

git init
git remote add origin [url]
git add .
git commit -m "1"
git push -u origin master

3.部署vuepress文档

详情可见:www.vuepress.cn/guide/deplo…

⑴在.vuepress目录下的config.js中配置base

我们还可以给站点增加一些配置:

⑵在项目根目录下,创建一个如下的 deploy.sh 文件(参考文档)

⑶在package.json下的scripts中配置发布命令:deploy:bash deploy.sh

在部署前我们先把本地代码提交到远程仓库:

git add .
git commit -m "1"
git push

然后执行命令:npm run deploy进行部署,项目会生成如下文件:

然后就可以在我们项目的pages中访问我们的站点了

image.png

最后附上地址:

文档:haoyue886.github.io/MUI/