封装简易个人UI组件库大体流程

242 阅读3分钟

我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

其实关于封装个人UI组件库不少大佬们早就已经开始了,我是属于一直再打算但是从未开始。也不属于口嗨,顶多是想象嗨~ 不过,虽迟但到。我还是来蹭一波~

首先是技术栈,要学要用就用最新的哈~ 那就是vite + vue3 + ts(其实也省去了对一些打包以及发布的配置,偷个懒~ )。哈哈,我是跟着掘金一个大佬的文章搭建的,vue3 + vite搭建流程。没有项目或者需要搭建vue3新项目的自行去搭建哈~

创建完新项目后,建议尽量将新项目自带的页面以及没用的组件删除掉。

组件代码

  • 第一步:在根目录下创建packages文件夹。

  • 第二步:在packages文件夹下新建一个自己的组件文件夹名为button。其中要提供两个文件分别为index.ts以及index.vueindex.vue文件则是放我们自己所需要封装的组件;index.ts是作为组件的一个出口,将同一目录下的index.vue引入注册并导出,这样该组件则可以被按需引入。

    需要注意的是:index.vue组件中必须要写name名字,否则注册时利用name则找不到该组件。

    (该button组件代码,非原创网上找的)

    image.png

<template>
  <button class="xy-button" :class="classes">
    <span v-if="loading" class="xy-loadingIndicator"></span>
    <slot></slot>
  </button>
</template>
<script>
  export default {
    name: 'xyButton' // 必须要写名字
  }
</script>
<script setup>
  import { computed } from 'vue';
  const props = defineProps({
    theme: {// 按钮形态
      type: String,
      default:"default" // primary, info, success, warning, error
    },
    dashed: { // 是否为虚线
      type: Boolean,
      default: false
    },
    size: { // button大小 small、large
      type: String,
      default: "default"
    },
    round: { // 是否有圆角
      type: Boolean,
      default: false
    },
    disabled: { // 是否禁用
      type: Boolean,
      default: false
    },
    loading: { // 是否为loading状态
      type: Boolean,
      default: false
    }
  });
  const { theme, dashed, size, round, disabled } = props;
  const classes = computed(() => {
    return {
      [`xy-theme-${theme}`]: theme,
      [`xy-theme-dashed`]: dashed,
      [`xy-theme-${size}`]: size,
      [`is-round`]: round,
      [`is-disabled`]: disabled,
    }
  })
</script>

<style lang="scss">
$h-default: 32px;
$h-small: 20px;
$h-large: 48px;
$white: #fff;
$default-color: #333;
$primary-color: #36ad6a;
$info-color: #4098fc;
$success-color: #85ce61;
$warning-color: #f0a020;
$error-color: #d03050;
$grey: grey;

$default-border-color: #d9d9d9;

$radius: 3px;
$green: #18a058;
.xy-button {
  box-sizing: border-box;
  height: $h-default;
  background-color: #fff;
  padding: 0 12px;
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  white-space: nowrap;
  border-radius: $radius;
  box-shadow: 0 1px 0 fade-out(black, 0.95);
  transition: all 250ms;
  color: $default-color;
  border: 1px solid $default-border-color;
  user-select: none;

  &:focus {
    outline: none;
  }

  &::-moz-focus-inner {
    border: 0;
  }

  &.xy-size-large {
    font-size: 24px;
    height: $h-large;
    padding: 0 16px;
  }
  &.xy-size-small {
    font-size: 12px;
    height: $h-small;
    padding: 0 8px;
  }

  &.is-round.xy-size-default {
    border-radius: calc($h-default / 2);
  }
  &.is-round.xy-size-large {
    border-radius: calc($h-large / 2);
  }
  &.is-round.xy-size-small {
    border-radius: calc($h-small / 2);
  }
  // button形态
  // 默认
  &.xy-theme-default {
    &:hover {
      color: $green;
      border-color: $green;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $green $green $green transparent;
      }
    }
    &:active {
      color: darken($green, 20%);
      border-color: darken($green, 20%);

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: darken($green, 20%) darken($green, 20%)
          darken($green, 20%) transparent;
      }
    }
    &.xy-theme-dashed {
      border-style: dashed;
    }
    > .xy-loadingIndicator {
      border-style: dashed;
      // border-color: $default-color $default-color $default-color transparent;
      border-color: $white $white $white transparent;
    }
  }
  // 常规
  &.xy-theme-primary {
    background-color: $primary-color;
    border-color: $primary-color;
    color: $white;

    &:hover {
      background: lighten($primary-color, 20%);
      border-color: lighten($primary-color, 20%);
    }
    &:active {
      background-color: darken($primary-color, 20%);
      border-color: darken($primary-color, 20%);
    }

    &.is-disabled {
      cursor: not-allowed;
      background: lighten($primary-color, 20%);
      border-color: lighten($primary-color, 20%);
      &:hover {
        background: lighten($primary-color, 20%);
        border-color: lighten($primary-color, 20%);
      }
    }

    &.xy-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $primary-color;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $primary-color $primary-color $primary-color transparent;
      }
    }
  }
  // 消息
  &.xy-theme-info {
    background-color: $info-color;
    border-color: $info-color;
    color: $white;
    &:hover {
      background: lighten($info-color, 20%);
      border-color: lighten($info-color, 20%);
    }
    &:active {
      background-color: darken($info-color, 20%);
      border-color: darken($info-color, 20%);
    }

    &.is-disabled {
      cursor: not-allowed;
      background: lighten($info-color, 20%);
      border-color: lighten($info-color, 20%);
      &:hover {
        background: lighten($info-color, 20%);
        border-color: lighten($info-color, 20%);
      }
    }

    &.xy-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $info-color;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $info-color $info-color $info-color transparent;
      }
    }
  }
  // 成功
  &.xy-theme-success {
    background-color: $success-color;
    border-color: $success-color;
    color: $white;
    &:hover {
      background: lighten($success-color, 20%);
      border-color: lighten($success-color, 20%);
    }
    &:active {
      background-color: darken($success-color, 20%);
      border-color: darken($success-color, 20%);
    }

    &.is-disabled {
      cursor: not-allowed;
      background: lighten($success-color, 20%);
      border-color: lighten($success-color, 20%);
      &:hover {
        background: lighten($success-color, 20%);
        border-color: lighten($success-color, 20%);
      }
    }

    &.xy-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $success-color;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $success-color $success-color $success-color transparent;
      }
    }
  }
  // 警告
  &.xy-theme-warning {
    background-color: $warning-color;
    border-color: $warning-color;
    color: $white;
    &:hover {
      background: lighten($warning-color, 20%);
      border-color: lighten($warning-color, 20%);
    }
    &:active {
      background-color: darken($warning-color, 20%);
      border-color: darken($warning-color, 20%);
    }

    &.is-disabled {
      cursor: not-allowed;
      background: lighten($warning-color, 20%);
      border-color: lighten($warning-color, 20%);
      &:hover {
        background: lighten($warning-color, 20%);
        border-color: lighten($warning-color, 20%);
      }
    }

    &.xy-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $warning-color;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $warning-color $warning-color $warning-color transparent;
      }
    }
  }
  // 错误
  &.xy-theme-error {
    background-color: $error-color;
    border-color: $error-color;
    color: $white;
    &:hover {
      background: lighten($error-color, 20%);
      border-color: lighten($error-color, 20%);
    }
    &:active {
      background-color: darken($error-color, 20%);
      border-color: darken($error-color, 20%);
    }

    &.is-disabled {
      cursor: not-allowed;
      background: lighten($error-color, 20%);
      border-color: lighten($error-color, 20%);
      &:hover {
        background: lighten($error-color, 20%);
        border-color: lighten($error-color, 20%);
      }
    }

    &.xy-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $error-color;

      > .xy-loadingIndicator {
        border-style: dashed;
        border-color: $error-color $error-color $error-color transparent;
      }
    }
  }

  > .xy-loadingIndicator {
    width: 14px;
    height: 14px;
    display: inline-block;
    margin-right: 4px;
    border-radius: 8px;
    border-color: $white $white $white transparent;
    border-style: solid;
    border-width: 2px;
    animation: xy-spin 1s infinite linear;
  }
}

@keyframes xy-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
</style>

image.png

import xyButton from './index.vue';
xyButton.install = (app:any) => {
  app.component(xyButton.name, xyButton)
}
export default xyButton;
  • 第三步:在packages文件夹下新建一个index.ts,用来管理这些组件。以便于使用者下载该组件库就可以使用其中所有的组件

    image.png

import xyButton from "./button/index";
// import annularMenu from "./BAnnularMenu/index";
const install = (app:any) => {
  app.use(xyButton)
  // pp.use(annularMenu)
};
const JUI = {
  install
}
export {
  xyButton,
  // annularMenu,
}
export default JUI;

image.png

如果此时需要本地测试使用,则在main.ts中添加以下代码

import { createApp } from 'vue'
import App from './App.vue'
import JUI from "../packages/index"
const app = createApp(App);
app.use(JUI)
app.mount('#app')

文件配置

在发布之前我们需要对文件进行打包。所以接下来:

  • 第一步:配置vite.config.ts文件。下方代码中build模块

    注意:此项目语言为ts,组件中不能包含js文件,会导致打包报错失败。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      external: ["vue"],// 打包发布时不需要vue依赖
      output: {
        globals: {
          vue: "Vue"
        }
      }
    },
    lib: {
        entry: './packages/index.ts', // 入口
        name: 'JUI'
    }
  },
  base: './', // 打包路径
  server: {
    port: 3000, // 服务端口号
    open: true, // 服务启动时是否自动打开浏览器
    cors: true // 允许跨域
  }
})

然后执行 yarn build 则会生成dist文件

image.png

  • 第二步:对package.json进行部分配置("scripts"上方处配置)
  "name": "xueyou-app",
  "private": false,// 第一次发布需要改为false,否则npm无法发布
  "version": "0.0.1", // 版本号
  // 下面所用为打包生成的文件
  "main": "./dist/xueyou-app.umd.js",
  "module": "./dist/xueyou-app.es.js",
  "exports": {
    ".": {
      "import": "./dist/xueyou-app.es.js",
      "require": "./dist/xueyou-app.umd.js"
    }
  },
  "files": [
    "dist/*"
  ],

发布

  • 第一步:需要进到npm的官网进行注册npm;

  • 第二步:在首次发布组件时需要执行 npm login 进行登陆。之后根据提示填写用户名,密码以及邮箱。第一次登陆需要一个实时的验证码,如果没开身份二次验证,则验证码会发送到邮箱。如果有兴趣可以添加身份二次验证About two-factor authentication | npm Docs (npmjs.com)

  • 第三步:通过以上验证后执行命令npm publish进行发布

发布后如果需要测试使用,则执行命令yarn add xxx

main.ts中添加以下代码:

import { createApp } from 'vue'
import App from './App.vue'
import JUI from xxx";
const app = createApp(App);
app.use(JUI)
app.mount('#app')

最后,只要页面中正常引用组件即可

也可以按需引入

需要使用组件的页面:

// 按需引入
import { xyButton } from 'xxx';

只是一个熟悉创建个人UI组件库的大体流程,在此之后如果自己有比较好的组件都可以放进来了。。。