booker-ui
v0.1.5
Published
A simple lib based on vite and vue!
Downloads
4
Readme
介绍
基于 Vite 的组件库开发实践,包含项目启动,组件文档,代码规范,单元测试和自动部署等内容。
实践过程
组件环境
- 新建文件夹
mkdir vite-ui && cd vite-ui
- 初始化项目
pnpm init -y(optional)
- 安装 Vite
pnpm i vite@latest -D
- 新建
/index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<div id="app">Hello world</div>
</body>
</html>
- 启动 Vite,访问
localhost:5173
测试是否访问正常
npx vite
- 新建
/src/index.ts
文件
const content: string = "Hello world from index.ts";
console.log(content);
- 修改
/index.html
文件,访问浏览器控制台测试是否输出日志
<!DOCTYPE html>
<html lang="en">
<head>
<!-- ... -->
</head>
<body>
<div id="app">Hello world</div>
<script src="./src/index.ts" type="module"></script>
</body>
</html>
- 修改
/package.json
文件,添加启动脚本(后续可以通过pnpm dev
使用)
{
"scripts": {
"dev": "vite"
}
}
组件开发
- 安装 Vue,添加对 Vue 的支持
pnpm i vue -D
- 新建
/src/button/index.ts
,编写一个使用render
的组件
import { defineComponent, h } from "vue";
export default defineComponent({
name: "VButton",
render() {
return h("button", null, "My Button");
},
});
- 修改
/src/index.ts
文件,创建 Vue 应用并引入组件,重启访问
import { createApp } from "vue";
import VButton from "./button/index";
createApp(VButton).mount("#app");
- 安装
@vitejs/plugin-vue
,添加对.vue
文件的支持
pnpm i @vitejs/plugin-vue -D
- 新建
/vite.config.ts
文件,将其添加到 Vite 插件中
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
export default defineConfig({
plugins: [vue()],
});
- 新建
/src/SFCButton/index.vue
文件
<template>
<button>SFC Button</button>
</template>
<script lang="ts">
export default {
name: "SFCButton",
};
</script>
- 修改
/src/index.ts
文件,引入创建的 SFC 组件
import { createApp } from "vue";
import SFCButton from "./SFCButton/index.vue";
createApp(SFCButton).mount("#app");
- 此时有报错,新建
/src/shims-vue.d.ts
为.vue
文件添加类型声明,重启访问
declare module ".vue" {
import { DefineComponent } from "vue";
const component: DefineComponent<null, null, any>;
export default component;
}
- 安装
@vitejs/plugin-vue-jsx
,添加.[t|j]sx
文件的支持
pnpm i @vitejs/plugin-vue-jsx -D
- 修改
/vite.config.ts
,将其添加到 Vite 插件中
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import jsx from "@vitejs/plugin-vue-jsx";
export default defineConfig({
plugins: [vue(), jsx()],
});
- 新建
/src/JSXButton/index.tsx
文件
import { defineComponent } from "vue";
export default defineComponent({
name: "JSXButton",
render() {
return <button>JSX Button</button>;
},
});
- 此时有报错,新建
/tsconfig.ts
文件为.tsx
文件提供类型声明
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types",
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["./**/*.*", "./src/shims-vue.d.ts"],
"exclude": ["node_modules"]
}
- 修改
/src/index.ts
文件,引入 JSX 组件,重启访问
import { createApp } from "vue";
import JSXButton from "./JSXButton/index";
createApp(JSXButton).mount("#app");
组件打包
- 新建
/src/entry.ts
文件
import Button from "./button/index";
import SFCButton from "./SFCButton/index.vue";
import JSXButton from "./JSXButton/index";
import { App } from "vue";
export { Button, SFCButton, JSXButton };
export default {
install(app: App) {
app.component(Button.name, Button);
app.component(SFCButton.name, SFCButton);
app.component(JSXButton.name, JSXButton);
},
};
- 修改
/vite.config.ts
文件,添加打包配置
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import jsx from "@vitejs/plugin-vue-jsx";
const rollupOptions = {
external: ["vue", "vue-router"],
output: {
globals: {
vue: "Vue",
},
},
};
export default defineConfig({
plugins: [vue(), jsx()],
build: {
rollupOptions,
minify: false,
lib: {
entry: "./src/entry.ts",
name: "ViteUI",
fileName: "vite-ui",
formats: ["es", "umd", "iife"],
},
},
});
- 修改
/package.json
文件,添加打包脚本
{
"scripts": {
"build": "vite build"
}
}
- 执行打包命令,会生成
/dist
文件夹
pnpm build
- 创建
/demo/esm/index.html
文件,重启访问http://localhost:5173/demo/esm/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite UI Demo</title>
</head>
<body>
<h1>Full Import</h1>
<div id="app"></div>
<script type="module">
import { createApp } from "vue/dist/vue.esm-bundler.js";
import ViteUI from "../../dist/vite-ui.mjs";
const rootComponent = {
template: `<VButton /> <SFCButton /> <JSXButton />`,
};
createApp(rootComponent).use(ViteUI).mount("#app");
</script>
</body>
</html>
- 创建
/demo/esm/button.html
文件,重启访问http://localhost:5173/demo/esm/button.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite UI Button Demo</title>
</head>
<body>
<h1>Single Import</h1>
<div id="app"></div>
<script type="module">
import { createApp } from "vue/dist/vue.esm-bundler.js";
import { SFCButton, JSXButton, Button } from "../../dist/vite-ui.mjs";
createApp({
template: `<VButton/> <JSXButton/> <SFCButton/>`,
})
.component(SFCButton.name, SFCButton)
.component(JSXButton.name, JSXButton)
.component(Button.name, Button)
.mount("#app");
</script>
</body>
</html>
组件样式
- 安装
unocss
(样式库)和@iconify-json/ic
(图标库)
pnpm i unocss @iconify-json/ic -D
- 修改
/vite.config.ts
文件,添加到 Vite 插件中
import css from "unocss/vite";
import { presetUno, presetAttributify, presetIcons } from "unocss";
export default defineConfig({
plugins: [
css({ presets: [presetUno(), presetAttributify(), presetIcons()] }),
],
});
- 修改
/src/JSXButton/index.tsx
文件,添加样式类
import { defineComponent } from "vue";
import "uno.css";
export default defineComponent({
name: "JSXButton",
setup(props, { slots }) {
return () => (
<button
class={`
py-2
px-4
font-semibold
text-white
bg-green-500
hover:bg-green-700
border-none
rounded
font-semibold
cursor-pointer
`}
>
{slots.default ? slots.default() : ""}
</button>
);
},
});
- 修改
/src/index.ts
文件,引入修改后的组件,重启访问
import { createApp } from "vue/dist/vue.esm-browser";
import ViteUI from "./entry";
createApp({ template: `<JSXButton>普通按钮</JSXButton>` })
.use(ViteUI)
.mount("#app");
- 修改
/src/JSXButton/index.tsx
文件,添加组件颜色属性
import { defineComponent, PropType } from "vue";
import "uno.css";
export type IColor =
| "black"
| "gray"
| "red"
| "yellow"
| "green"
| "blue"
| "indigo"
| "purple"
| "pink";
export const JSXButtonProps = {
color: {
type: String as PropType<IColor>,
default: "blue",
},
};
export default defineComponent({
name: "JSXButton",
props: JSXButtonProps,
setup(props, { slots }) {
return () => (
<button
class={`
py-2
px-4
font-semibold
text-white
bg-${props.color}-500
hover:bg-${props.color}-700
border-none
rounded
font-semibold
cursor-pointer
`}
>
{slots.default ? slots.default() : ""}
</button>
);
},
});
- 此时颜色不生效,新建
/config/unocss.ts
文件
import { presetUno, presetAttributify, presetIcons } from "unocss";
import Unocss from "unocss/vite";
const colors = [
"white",
"black",
"gray",
"red",
"yellow",
"green",
"blue",
"indigo",
"purple",
"pink",
];
const safelist = [
...colors.map((v) => `bg-${v}-500`),
...colors.map((v) => `hover:bg-${v}-700`),
];
export default () => {
return Unocss({
safelist,
presets: [presetUno(), presetAttributify(), presetIcons()],
});
};
- 修改
/vite.config.ts
文件,更新插件的引入方式,重启访问
import css from "./config/unocss";
export default defineConfig({
plugins: [css()],
});
- 修改
/config/unocss.ts
文件,添加安全的图标列表
const icones = [
"search",
"edit",
"check",
"message",
"star-off",
"delete",
"add",
"share",
];
const safelist = [
// ...
...icones.map((v) => `i-ic-baseline-${v}`),
];
- 修改
/src/JSXButton/index.tsx
文件,添加图标属性
export type IIcon =
| "search"
| "edit"
| "check"
| "message"
| "star-off"
| "delete"
| "add"
| "share";
export const JSXButtonProps = {
//...
icon: {
type: String as PropType<IIcon>,
},
};
export default defineComponent({
name: "JSXButton",
props: JSXButtonProps,
setup(props, { slots }) {
return () => (
<button class={/* .. */}>
{props.icon && <i class={`i-ic-baseline-${props.icon} p-3`}></i>}
{slots.default ? slots.default() : ""}
</button>
);
},
});
- 编辑
/src/index.ts
文件,导入修改后的组件
import { createApp } from "vue/dist/vue.esm-browser";
import ViteUI from "./entry";
createApp({
template: `
<div style="display: flex; gap: 16px; margin-top: 24px;">
<JSXButton icon="search" >搜索图标</JSXButton>
<JSXButton icon="edit" >编辑图标</JSXButton>
<JSXButton icon="check" >检查图标</JSXButton>
<JSXButton icon="message" >消息图标</JSXButton>
<JSXButton icon="delete" >删除图标</JSXButton>
</div>
`,
})
.use(ViteUI)
.mount("#app");
- 此时打包会出错,修改
/vite.config.ts
文件,单独导出 css
export default defineConfig({
build: {
// ...
cssCodeSplit: true,
},
});
- 运行打包命令,
/dist
目录下会生成assets/entry.xxx.css
pnpm build
- 修改
/demo/esm/index.html
文件,导入样式和修改组件调用方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite UI Demo</title>
<link rel="stylesheet" href="../../dist/assets/entry.c7412cfc.css" />
</head>
<body>
<h1>Full Import</h1>
<div id="app"></div>
<script type="module">
import { createApp } from "vue/dist/vue.esm-bundler.js";
import ViteUI from "../../dist/vite-ui.mjs";
const rootComponent = {
template: `<VButton /> <SFCButton /> <JSXButton type="red" icon="search">测试按钮</JSXButton>`,
};
createApp(rootComponent).use(ViteUI).mount("#app");
</script>
</body>
</html>
组件文档
- 安装
Vitepress
pnpm i vitepress -D
- 新建
/docs/vite.config.ts
文件
import { defineConfig } from "vite";
import jsx from "@vitejs/plugin-vue-jsx";
import css from "../config/unocss";
export default defineConfig({
plugins: [jsx(), css()],
server: {
port: 5000,
},
});
- 新建
/docs/index.md
文件
# Vite UI
- 修改
/package.json
文件,添加启动脚本
{
"scripts": {
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"docs:serve": "vitepress serve docs"
}
}
- 运行启动命令,访问
localhost:5000
pnpm docs:dev
- 新建
/docs/.vitepress/config.ts
文件,添加配置,重启访问
import { defineConfig } from "vitepress";
const config = defineConfig({
themeConfig: {
sidebar: [
{
text: "组件",
items: [
{ text: "快速开始", link: "/" },
{
text: "通用",
items: [{ text: "Button 按钮", link: "/components/button/" }],
},
{ text: "导航", link: "/nav" },
{ text: "反馈", link: "/feedback" },
{ text: "数据录入", link: "/input" },
{ text: "数据展示", link: "/output" },
{ text: "布局", link: "/layout" },
],
},
],
},
});
export default config;
- 新建
/docs/.vitepress/theme/index.ts
文件,添加组件
import { Theme } from "vitepress";
import DefaultTheme from "vitepress/theme";
import ViteUI from "../../../src/entry";
import 'uno.css'
const themeConfig: Theme = {
...DefaultTheme,
enhanceApp({ app }) {
app.use(ViteUI);
},
};
export default themeConfig;
- 修改
/docs/index.md
文件,使用导入的组件,重启访问
# Vite UI
<div style="margin-bottom:20px;">
<JSXButton color="blue">主要按钮</JSXButton>
<JSXButton color="green">绿色按钮</JSXButton>
<JSXButton color="gray">灰色按钮</JSXButton>
<JSXButton color="yellow">黄色按钮</JSXButton>
<JSXButton color="red">红色按钮</JSXButton>
</div>
- 安装
vitepress-theme-demoblock
,用于组件示例
pnpm i [email protected] -D
- 修改
/docs/.vitepress/config.ts
文件,使用该插件
import { defineConfig } from "vitepress";
import { demoBlockPlugin } from "vitepress-theme-demoblock";
const config = defineConfig({
themeConfig: {
sidebar: [
{
text: "组件",
items: [
{ text: "快速开始", link: "/" },
{
text: "通用",
items: [{ text: "Button 按钮", link: "/components/button/" }],
},
{ text: "导航", link: "/nav" },
{ text: "反馈", link: "/feedback" },
{ text: "数据录入", link: "/input" },
{ text: "数据展示", link: "/output" },
{ text: "布局", link: "/layout" },
],
},
],
},
markdown: {
config: (md) => {
md.use(demoBlockPlugin);
},
},
});
export default config;
- 修改
/docs/.vitepress/theme/index.ts
文件,注册组件
import { Theme } from "vitepress";
import DefaultTheme from "vitepress/theme";
import ViteUI from "../../../src/entry";
import "vitepress-theme-demoblock/theme/styles/index.css";
import Demo from "vitepress-theme-demoblock/components/Demo.vue";
import DemoBlock from "vitepress-theme-demoblock/components/DemoBlock.vue";
const themeConfig: Theme = {
...DefaultTheme,
enhanceApp({ app }) {
app.use(ViteUI);
app.component('Demo', Demo);
app.component('DemoBlock', DemoBlock);
},
};
export default themeConfig;
- 修改
/docs/index.md
文件,添加组件示例,重启访问
:::demo 使用`size`、`color`、`pain`、`round`属性来定义 Button 的样式。
#```vue
<template>
<div style="display: flex; gap: 16px; margin-bottom:20px;">
<JSXButton color="blue">主要按钮</JSXButton>
<JSXButton color="green">绿色按钮</JSXButton>
<JSXButton color="gray">灰色按钮</JSXButton>
<JSXButton color="yellow">黄色按钮</JSXButton>
<JSXButton color="red">红色按钮</JSXButton>
</div>
#```
:::
组件测试
- 安装以下3个依赖
pnpm i vitest happy-dom @vue/test-utils -D
- 修改
/vite.config.ts
文件,添加测试配置
/// <reference types="vitest" />
export default defineConfig({
// ...
test: {
globals: true,
environment: 'happy-dom',
transformMode: {
web: [/.[tj]sx$/]
}
}
});
- 新建
/src/JSXButton/__tests__/JSXButton.spec.ts
文件,添加测试用例
import { describe, expect, test } from "vitest";
import JSXButton from "..";
import { shallowMount } from '@vue/test-utils';
describe('Button', () => {
test('mount @vue/test-utils', () => {
const wrapper = shallowMount(JSXButton, {
slots: {
default: 'Button'
}
})
expect(wrapper.text()).toBe('Button')
})
})
- 修改
package.json
文件,添加测试脚本
{
"scripts": {
"test": "vitest"
}
}
- 运行测试命令,查看测试结果
pnpm test
代码规范
- 安装以下依赖,我也不太清楚干啥用的(虽然有别的方法,暂时先这样)
pnpm i eslint -D
# ESLint 专门解析 TypeScript 的解析器
pnpm i @typescript-eslint/parser -D
# 内置各种解析 TypeScript rules 插件
pnpm i @typescript-eslint/eslint-plugin -D
pnpm i eslint-formatter-pretty -D
pnpm i eslint-plugin-json -D
pnpm i eslint-plugin-prettier -D
pnpm i eslint-plugin-vue -D
pnpm i @vue/eslint-config-prettier -D
pnpm i babel-eslint -D
pnpm i prettier -D
- 新建
/.eslintrc.cjs
文件,添加配置
module.exports = {
root: true,
env: {
browser: true,
es2020: true,
node: true,
jest: true
},
globals: {
ga: true,
chrome: true,
__DEV__: true
},
// 解析 .vue 文件
parser: 'vue-eslint-parser',
extends: [
'plugin:json/recommended',
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/prettier'
],
plugins: ['@typescript-eslint'],
parserOptions: {
parser: '@typescript-eslint/parser' // 解析 .ts 文件
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'prettier/prettier': 'error'
}
}
- 新建
/.eslintignore
文件,添加忽略文件
*.sh
node_modules
lib
coverage
*.md
*.scss
*.woff
*.ttf
src/index.ts
dist
- 修改
/package.json
文件,添加检查和格式化脚本
{
"scripts": {
"lint": "eslint --fix --ext .ts,.vue src",
"format": "prettier --write \"src/**/*.ts\" \"src/**/*.vue\"",
},
}
- 运行检查命令,查看检查结果
pnpm lint
- 安装
husky
,用于定义Git Hooks
pnpm i husky -D
- 通过以下命令,添加脚本到
/package.json
文件中
npm set-script prepare "husky install"
- 添加Git声明周期钩子
mkdir .husky && npx husky add .husky/pre-commit "pnpm run lint"
- 测试是否有效
git commit -m "feat: commint for lint test"
- 执行命令,添加测试钩子
npx husky add .husky/pre-push "pnpm test:run"
- 修改
/package.json
文件
{
"scripts": {
"test:run": "vitest run",
}
}
- 安装以下依赖,用于检测提交信息
# 安装commitlint
pnpm i -d @commitlint/config-conventional@"17.0.2" @commitlint/cli@"17.0.2"
# Configure commitlint to use conventional config
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
- 执行命令,添加测试钩子
npx husky add .husky/commit-msg "npx --no -- commitlint --edit \"$1\""
部署
- 修改
/package.json
,指定导出目录和内容
{
"main": "./dist/vite-ui.umd.js",
"module": "./dist/vite-ui.mjs",
"files": ["dist"],
}
- 登录npm
npm login
- 发布代码
npm publish
- 访问代码
https://www.npmjs.com/package/booker-ui