VUE3 组件库学习之路:Button组件(开发、调试、打包、发布)

167 阅读6分钟

image.png

开发组件库从按钮开始,从这个比较简单的组件入手,体验开发、调试、打包、发布的整个流程。组件目录结构为:

├─ examples
├─ packages
│  ├─ components
│  │  ├─ package.json
│  │  ├─ src
│  │  │  ├─ Button
│  │  │  │  ├─ index.scss
│  │  │  │  ├─ index.ts
│  │  │  │  └─ index.vue
│  │  │  ├─ common.css
│  │  │  ├─ constants.ts
│  │  │  └─ index.ts
│  │  ├─ tsconfig.json
└─ └─ └─ vite.config.ts

组件功能介绍

按钮属性props

  • type plain 按钮的基本样式;
  • size 按钮尺寸大小;
  • round slant circle 按钮的形状;
  • disabled 是否可点击;
  • hoverType 鼠标hover的样式;

按钮事件 emits

  • clickprops.disabledfalse 时不可点击。

基本样式 type plain

type包含 default primary success warning danger

plainboolean 类型;

  1. Button/index.vue 根据传入的不同类型增加不同的样式 class: j_button-${props.type}is-plain
  2. common.css 定义通用样式及变量;
  3. Button/index.css 定义不同 class 对应的样式及变量;

组件开发

<script setup lang="ts">
import { computed } from 'vue';
import './index.scss';

const types = ['', 'primary', 'success', 'warning', 'danger'];

const props = defineProps({
    "type": {
        "type": String,
        "values": constants.types,
        "default": 'default'
    },
    "plain": Boolean
});
const styleClass = computed(() => {
    return {
        [`j_button-${props.type}`]: props.type,
        "is_plain": props.plain
    };
});
</script>
<script lang="ts">
export default {
    "name": 'JButton'
};
</script>

<template>
    <button class="j_button" :class="styleClass">
        <slot></slot>
    </button>
</template>

样式开发

common.css 定义一些通用样式的变量,如颜色、尺寸等。

:root {
  --el-font-size-base: 14px;
  --j-border-radius-base: 4px;
  --j-border-radius-small: 2px;
  --j-border-radius-round: 20px;
  --j-color-primary: #3f51b5;
  --j-color-primary-light: #949dd7;
  --j-color-primary-light1: #5766bb;
  --j-color-primary-light2: #7986cb;
  --j-color-primary-light3: #c0c6ef;
  --j-color-primary-light4: #e9eaf6;
  --j-color-success: #28b78d;
  --j-color-success-light: #87c5b2;
  --j-color-success-light1: #61c5a4;
  --j-color-success-light2: #83d1b7;
  --j-color-success-light3: #caede3;
  --j-color-success-light4: #e1f3d8;
  --j-color-warning: #cb892e;
  --j-color-warning-light: #dfba84;
  --j-color-warning-light1: #d6a15c;
  --j-color-warning-light2: #fec475;
  --j-color-warning-light3: #fed5a2;
  --j-color-warning-light4: #ffe5c7;
  --j-color-error: #ff5964;
  --j-color-error-light: #efaeae;
  --j-color-error-light1: #ff7683;
  --j-color-error-light2: #ff909c;
  --j-color-error-light3: #ffa9b3;
  --j-color-error-light4: #fef0f0;
}

Button/index.css 定义按钮的相关变量以及样式,会用到 common.css 的变量。

.j_button {
    --j-button-text-color: #606266;
    --j-button-bg-color: #fff;
    --j-button-border-color: #dcdfe6;
    --j-button-hover-text-color: var(--j-color-primary);
    --j-button-hover-bg-color: var(--j-color-primary-light4);
    --j-button-hover-border-color: var(--j-color-primary);
    --j-button-active-bg-color: var(--j-color-primary-light1);
    --j-button-active-text-color: var(--j-color-primary);
    --j-button-active-border-color: var(--j-color-primary);
    --j-button-disabled-text-color: #a8abb2;
    --j-button-disabled-bg-color: var(--j-button-bg-color);
    --j-button-disabled-border-color: #e4e7ed;
    --el-button-size: 32px;
    --j-button-border: 1px solid var(--j-button-border-color);
}
.j_button {
    border-radius: var(--j-border-radius-base);
    border: 1px solid transparent;
    padding: 8px 15px;
    height: var(--el-button-size);
    font-size: var(--el-font-size-base);
    background-color: var(--j-button-bg-color);
    color: var(--j-button-text-color);
    border-color: var(--j-button-border-color);
    border: var(--j-button-border);
    cursor: pointer;
    transition: all 0.25s;
    position: relative;
    z-index: 0;
}
.j_button:hover,
.j_button::active, 
.j_button::focus,
.j_button::focus-visible {
    color: var(--j-button-hover-text-color);
    border-color: var(--j-button-hover-border-color);
    background-color: var(--j-button-hover-bg-color);
    outline: 0;
}

type plain 传入不同的值,会生成不同的 class,主要是通过控制不同 class 的变量值不同控制按钮的展示样式。👇以primary类型为例,其余类型控制方式相同,知识色值不同。

// Button/index.css
.j_button.j_button-primary {
    --j-button-text-color: #fff;
    --j-button-bg-color: var(--j-color-primary);
    --j-button-border-color: var(--j-color-primary);
    --j-button-hover-text-color: #fff;
    --j-button-hover-bg-color: var(--j-color-primary-light1);
    --j-button-hover-border-color: var(--j-color-primary-light1);
    --j-button-active-bg-color: var(--j-color-primary-light1);
    --j-button-active-text-color: #fff;
    --j-button-active-border-color: var(--j-color-primary-light1);
    --j-button-disabled-text-color: var(--j-color-primary-light);
    --j-button-disabled-bg-color: var(--j-color-primary-light4);
    --j-button-disabled-border-color: var(--j-color-primary-light2);
}
.j_button.j_button-primary.is_plain {
    --j-button-bg-color: var(--j-color-primary-light3);
    --j-button-text-color: var(--j-color-primary);
    --j-button-hover-bg-color: var(--j-color-primary);
}

size 尺寸

size 包含 default small large 三种尺寸;

根据传入不同的 size 生成不同的样式class: j_button-${props.size}

// Button/index.vue
const props = defineProps({
    "size": {
        "type": String,
        "value": constants.componentSizes
    }
});
const styleClass = computed(() => {
    return {
        [`j-button-${props.size}`]: props.size
    };
});
// Button/index.css
.j_button.j-button-small {
    --el-button-size: 24px;
    padding: 5px 11px;
    font-size: 12px;
}
.j_button.j-button-large {
    --el-button-size: 40px;
    padding: 12px 19px;
}

round slant circle 按钮的形状

round胶囊形、 circle圆形、slant 平行四边形;接受参数类型均为Boolean类型。

// Button/index.vue
const props = defineProps({
    "round": Boolean,
    "slant": Boolean,
    "circle": Boolean
});
const styleClass = computed(() => {
    return {
        "is_round": props.round,
        "is_slant": props.slant,
        "is_circle": props.circle
    };
});
// Button/index.css
.j_button.is_round {
    border-radius: var(--j-border-radius-round);
}
.j_button.is_circle {
    border-radius: 50%;
    padding: 8px;
}
.j_button.is_slant {
    border: transparent;
    background-color: transparent;
}
.j_button.is_slant::before {
    position: absolute;
    top: 0;
    left: 0;
    z-index: -1;
    height: 100%;
    content: '';
    transform: skewX(20deg);
    width: 100%;
    border: var(--j-button-border);
    box-sizing: border-box;
    background-color: var(--j-button-bg-color);
    transition: box-shadow .2s cubic-bezier(.02,.01,.47,1);
}
.j_button.is_slant:hover {
    border: transparent;
    background-color: transparent;
}
.j_button.is_slant:hover::before,
.j_button.is_slant:active::before, 
.j_button.is_slant:focus::before,
.j_button.is_slant:focus-visible::before {
    border-color: var(--j-button-hover-border-color);
    background-color: var(--j-button-hover-bg-color);
}

disabled 不可点击

disabled 同样接受 Boolean 类型参数,css 设置不可点击的色值变量和鼠标样式。

// Button/index.vue
const props = defineProps({
    "disabled": Boolean
});
const styleClass = computed(() => {
    return {
        "is_disabled": props.disabled
    };
});
// Button/index.css
.j_button.is_disabled {
    --j-button-text-color: var(--j-button-disabled-text-color);
    --j-button-bg-color: var(--j-button-disabled-bg-color);
    --j-button-border-color: var(--j-button-disabled-border-color);
    --j-button-hover-text-color: var(--j-button-text-color);
    --j-button-hover-bg-color: var(--j-button-bg-color);
    --j-button-hover-border-color: var(--j-button-border-color);
}
.j_button.is_disabled {
    cursor: not-allowed;
}

hoverType hover样式

hoverType 包含 fill fill-pill fill-unite

主要是通过控制:before :after 样式实现的hover效果。

buttons example

本地调试

导出组件

  1. 按钮组件导出。
// packages/components/Button/index.ts
import { App } from 'vue';
import JButton from './index.vue';

JButton.install = (app: App) => {
    app.component(JButton.name, JButton);
};

export default JButton;
  1. 导出整个组件库,支持全局导入单个组件导入
// packages/components/index.ts
import { App, Plugin } from 'vue';
import JButton from './Button';
import JIcon from './Icon';
const components = {
    JButton,
    JIcon
};
const UnclePlugin: Plugin = {
    install(app: App) {
        Object.values(components).forEach(item => {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            app.use(item);
        });
    }
};

export default UnclePlugin;

export { default as JButton } from './Button';
export { default as JIcon } from './Icon';
  1. 修改入口文件,改为index.ts
// packages/components/package.json
"main": "./src/index.ts"

本地调用

examples 目录下存放的是组件示例,引入本地开发的

  1. 安装依赖 @unclejia/components
pnpm --filter @unclejia/examples install @unclejia/components

跟目录下配置了 workspaces 所以本地安装 @unclejia/components 安装包链接到路径 packages/components

  1. 使用组件
// examples/main.ts
import { createApp } from 'vue';
import './style.css';
import App from './App.vue';
import { JButton } from '@unclejia/components';

createApp(App)
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    .use(JButton)
    .mount('#app');
// Home.vue
<j-button>Default</j-button>
  1. 启动项目
pnpm run dev

跟目录下 package.json 配置过 "dev": "pnpm run -C examples dev", 启动的项目为 examples

打包组件库

将来 @unclejia/components 会发布到 npm 上,发布的包就是打包之后的lib,下面来看一下本地使用打包后的组件库需要做的修改有哪些。

  1. 使用vite打包,首先安装依赖
pnpm --filter @unclejia/components install vite @vitejs/plugin-vue -D
  1. package.json增加一个运行脚本

"build": "vue-tsc && vite build"

  1. 创建配置文件 vite.config.ts 打包成库模式
// packages/components/vite.config.ts
import { resolve } from 'path';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    "plugins": [vue()],
    "build": {
        // 指定输出路径为 lib
        "outDir": 'lib',
        // 库模式构建配置
        "lib": {
            "entry": resolve(__dirname, './src/index.ts'),
            "name": '@unclejia/components',
            "formats": ['es'],
            "fileName": (format) => `unclejia-components.${format}.js`
        },
        "rollupOptions": {
            "external": ['vue'],
            "output": {
                // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
                "globals": {
                    "vue": 'Vue'
                }
            }
        },
        "emptyOutDir": false // 确保在执行tsc的时候生成types
    }
});
  1. 修改组件库入口 main 和 类型文件 types
// packages/components/package.json
"main": "./lib/unclejia-components.es.js",
"types": "./lib/types/index.d.ts",
  1. examples 引入样式文件
// examples/src/main.ts
import '@unclejia/components/lib/style.css';

发布

首先在 npm 官网注册账号。如果使用了@XX/XXX (例如@unclejia/components)则需要在 npm 官网新建一个名为 unclejia 的组织。

将所有代码都提交到 git 仓库,执行发布命令 pnpm publish 第一次发布会要求输入 npm 官网注册的账号密码。

@unclejia/components 包仅发布组件库代码,所以发布时需要先进入到组件库文件夹内。

cd packages/components
pnpm publish --access public
cd -

发布完成后可以在 npm 中找到发布的包 www.npmjs.com/package/@un…

按钮预览 Button