npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

booker-ui

v0.1.5

Published

A simple lib based on vite and vue!

Downloads

4

Readme

介绍

基于 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 [email protected] -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 的样式。
#```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>
#```
:::

组件测试

  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

参考链接