0.1.5 • Published 2 years ago

booker-ui v0.1.5

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

介绍

基于 Vite 的组件库开发实践,包含项目启动,组件文档,代码规范,单元测试和自动部署等内容。

实践过程

组件环境

  1. 新建文件夹
mkdir vite-ui && cd vite-ui
  1. 初始化项目
pnpm init -y(optional)
  1. 安装 Vite
pnpm i vite@latest -D
  1. 新建/index.html文件
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
  </head>
  <body>
    <div id="app">Hello world</div>
  </body>
</html>
  1. 启动 Vite,访问localhost:5173测试是否访问正常
npx vite
  1. 新建/src/index.ts文件
const content: string = "Hello world from index.ts";
console.log(content);
  1. 修改/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>
  1. 修改/package.json文件,添加启动脚本(后续可以通过pnpm dev使用)
{
  "scripts": {
    "dev": "vite"
  }
}

组件开发

  1. 安装 Vue,添加对 Vue 的支持
pnpm i vue -D
  1. 新建/src/button/index.ts,编写一个使用render的组件
import { defineComponent, h } from "vue";

export default defineComponent({
  name: "VButton",
  render() {
    return h("button", null, "My Button");
  },
});
  1. 修改/src/index.ts文件,创建 Vue 应用并引入组件,重启访问
import { createApp } from "vue";
import VButton from "./button/index";

createApp(VButton).mount("#app");
  1. 安装@vitejs/plugin-vue,添加对.vue文件的支持
pnpm i @vitejs/plugin-vue -D
  1. 新建/vite.config.ts文件,将其添加到 Vite 插件中
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
});
  1. 新建/src/SFCButton/index.vue文件
<template>
  <button>SFC Button</button>
</template>
<script lang="ts">
export default {
  name: "SFCButton",
};
</script>
  1. 修改/src/index.ts文件,引入创建的 SFC 组件
import { createApp } from "vue";
import SFCButton from "./SFCButton/index.vue";

createApp(SFCButton).mount("#app");
  1. 此时有报错,新建/src/shims-vue.d.ts.vue文件添加类型声明,重启访问
declare module ".vue" {
  import { DefineComponent } from "vue";
  const component: DefineComponent<null, null, any>;
  export default component;
}
  1. 安装@vitejs/plugin-vue-jsx,添加.[t|j]sx文件的支持
pnpm i @vitejs/plugin-vue-jsx -D
  1. 修改/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()],
});
  1. 新建/src/JSXButton/index.tsx文件
import { defineComponent } from "vue";

export default defineComponent({
  name: "JSXButton",
  render() {
    return <button>JSX Button</button>;
  },
});
  1. 此时有报错,新建/tsconfig.ts文件为.tsx文件提供类型声明
{
  "compilerOptions": {
    "declaration": true,
    "declarationDir": "./dist/types",
    "jsx": "preserve",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true
  },
  "include": ["./**/*.*", "./src/shims-vue.d.ts"],
  "exclude": ["node_modules"]
}
  1. 修改/src/index.ts文件,引入 JSX 组件,重启访问
import { createApp } from "vue";
import JSXButton from "./JSXButton/index";

createApp(JSXButton).mount("#app");

组件打包

  1. 新建/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);
  },
};
  1. 修改/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"],
    },
  },
});
  1. 修改/package.json文件,添加打包脚本
{
  "scripts": {
    "build": "vite build"
  }
}
  1. 执行打包命令,会生成/dist文件夹
pnpm build
  1. 创建/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>
  1. 创建/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>

组件样式

  1. 安装unocss(样式库)和@iconify-json/ic(图标库)
pnpm i unocss @iconify-json/ic -D
  1. 修改/vite.config.ts文件,添加到 Vite 插件中
import css from "unocss/vite";
import { presetUno, presetAttributify, presetIcons } from "unocss";

export default defineConfig({
  plugins: [
    css({ presets: [presetUno(), presetAttributify(), presetIcons()] }),
  ],
});
  1. 修改/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>
    );
  },
});
  1. 修改/src/index.ts文件,引入修改后的组件,重启访问
import { createApp } from "vue/dist/vue.esm-browser";
import ViteUI from "./entry";

createApp({ template: `<JSXButton>普通按钮</JSXButton>` })
  .use(ViteUI)
  .mount("#app");
  1. 修改/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>
    );
  },
});
  1. 此时颜色不生效,新建/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()],
  });
};
  1. 修改/vite.config.ts文件,更新插件的引入方式,重启访问
import css from "./config/unocss";

export default defineConfig({
  plugins: [css()],
});
  1. 修改/config/unocss.ts文件,添加安全的图标列表
const icones = [
  "search",
  "edit",
  "check",
  "message",
  "star-off",
  "delete",
  "add",
  "share",
];

const safelist = [
  // ...
  ...icones.map((v) => `i-ic-baseline-${v}`),
];
  1. 修改/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>
    );
  },
});
  1. 编辑/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");
  1. 此时打包会出错,修改/vite.config.ts文件,单独导出 css
export default defineConfig({
  build: {
    // ...
    cssCodeSplit: true,
  },
});
  1. 运行打包命令,/dist目录下会生成assets/entry.xxx.css
pnpm build
  1. 修改/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>

组件文档

  1. 安装Vitepress
pnpm i vitepress -D
  1. 新建/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,
  },
});
  1. 新建/docs/index.md文件
# Vite UI
  1. 修改/package.json文件,添加启动脚本
{
  "scripts": {
    "docs:dev": "vitepress dev docs",
    "docs:build": "vitepress build docs",
    "docs:serve": "vitepress serve docs"
  }
}
  1. 运行启动命令,访问localhost:5000
pnpm docs:dev
  1. 新建/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;
  1. 新建/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;
  1. 修改/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>
  1. 安装vitepress-theme-demoblock,用于组件示例
pnpm i vitepress-theme-demoblock@1.0.0-alpha.10 -D
  1. 修改/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;
  1. 修改/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;
  1. 修改/docs/index.md文件,添加组件示例,重启访问
:::demo 使用`size`、`color`、`pain`、`round`属性来定义 Button 的样式。
#```js
<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>
#```
:::

组件测试

  1. 安装以下3个依赖
pnpm i vitest happy-dom @vue/test-utils -D
  1. 修改/vite.config.ts文件,添加测试配置
/// <reference types="vitest" />

export default defineConfig({
  // ...
  test: {
    globals: true,
    environment: 'happy-dom',
    transformMode: {
      web: [/.[tj]sx$/]
    }
  }
});
  1. 新建/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')
  })
})
  1. 修改package.json文件,添加测试脚本
{
  "scripts": {
    "test": "vitest"
  }
}
  1. 运行测试命令,查看测试结果
pnpm test

代码规范

  1. 安装以下依赖,我也不太清楚干啥用的(虽然有别的方法,暂时先这样)
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
  1. 新建/.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'
  }
}
  1. 新建/.eslintignore文件,添加忽略文件
*.sh
node_modules
lib
coverage
*.md
*.scss
*.woff
*.ttf
src/index.ts
dist
  1. 修改/package.json文件,添加检查和格式化脚本
{
  "scripts": {
    "lint": "eslint --fix --ext .ts,.vue src",
    "format": "prettier --write \"src/**/*.ts\" \"src/**/*.vue\"",
  },
}
  1. 运行检查命令,查看检查结果
pnpm lint
  1. 安装husky,用于定义Git Hooks
pnpm i husky -D
  1. 通过以下命令,添加脚本到/package.json文件中
npm set-script prepare "husky install"
  1. 添加Git声明周期钩子
mkdir .husky && npx husky add .husky/pre-commit "pnpm run lint"
  1. 测试是否有效
git commit -m "feat: commint for lint test"
  1. 执行命令,添加测试钩子
npx husky add .husky/pre-push "pnpm test:run"
  1. 修改/package.json文件
{
  "scripts": {
    "test:run": "vitest run",
  }
}
  1. 安装以下依赖,用于检测提交信息
# 安装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
  1. 执行命令,添加测试钩子
npx husky add .husky/commit-msg "npx --no -- commitlint --edit \"$1\""

部署

  1. 修改/package.json,指定导出目录和内容
{
  "main": "./dist/vite-ui.umd.js",
  "module": "./dist/vite-ui.mjs",
  "files": ["dist"],
}
  1. 登录npm
npm login
  1. 发布代码
npm publish
  1. 访问代码
https://www.npmjs.com/package/booker-ui

参考链接

0.1.5

2 years ago

0.1.4

2 years ago

0.1.3

2 years ago

0.1.2

2 years ago

0.1.1

2 years ago

0.1.0

2 years ago