Vite+Vue3 开发UI组件库并发布到npm

        一直对开源UI组件库比较感兴趣,摸索着开发了一套,虽然还只是开始,但是从搭建到发布这套流程基本弄明白了,现在分享给大家,希望对同样感兴趣的同学有所帮助。

        目前我的这套名为hasaki-ui的组件库仅有两个组件,大致成果如下,后续有时间会继续完善。

         该项目采用的技术栈为Vite + Vue3,还使用了一些基本的Markdown知识,阅读本文档前,希望你至少对vue有些基础,那么我们正式开始。

一、创建一个Vite项目

        参照vite官网,打开命令行,执行上述命令之一,按提示操作即可,创建完成后,你得到了一个以你创建的项目名为名的一个文件夹,我的为hasaki-ui,直接以成品进行说明吧。

二、组件开发 

         本文重点不在于某个组件的开发,而是库开发的配置、发布流程,所以在此以一个很简单的demo组件作为说明。

        doc.md为组件的说明文档,一般是用 Markdown 来写。这里我们需要使用 vite-plugin-vue-markdown插件来将 md 文件转换成 vue 文件。

// doc.md
<script setup>
import doc from './doc.vue';
import PreviewCode from '@/components/PreviewCode.vue'
</script>

# DEMO

<doc/>
<PreviewCode compName="demo" />

doc.vue 为展示demo组件的vue文件

// doc.vue
<template>
  <div class="demo-doc">
    <Demo />
  </div>
</template>
<script lang="ts">
export default {
  name: 'DemoDoc'
}
</script>
<script lang="ts" setup></script>
<style lang="scss" scoped>
.demo-doc {
  width: 100%;
  height: 100%;
  padding: 20px;
}
</style>

 库组件

// Demo.vue
<template>
  <div class="demo">
    Demo is here
  </div>
</template>
<script lang="ts">
export default {
  name: 'Demo'
}
</script>
<script lang="ts" setup>
import { onMounted } from 'vue'

onMounted(() => {
  console.log('demo 出现啦')
})
// endregion
</script>
<style lang="scss" scoped>
.demo {
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
</style>

 PreviewCode.vue组件用于获取组件源码,用于在页面展示源码

// PreviewCode.vue
<template>
  <div class="preview-code">
    <div class="showCode" @click="showOrhideCode">
      <span>{{ showCode ? '隐藏代码' : '显示代码' }} &lt; &gt;</span>
    </div>
    <el-scrollbar>
      <transition>
        <pre v-highlight v-if="showCode">
          <code>{{ sourceCode }}</code>
        </pre>
      </transition>
    </el-scrollbar>
  </div>
</template>

<script setup>
import { onMounted, ref } from 'vue'

const props = defineProps({
  compName: {
    type: String,
    default: '',
    require: true
  }
})

const showCode = ref(false)
const sourceCode = ref('')

const showOrhideCode = () => {
  showCode.value = !showCode.value
}

const getSourceCode = async () => {
  let code = await import(
    /* @vite-ignore */ `@/components/hasaki-ui/${props.compName}/doc/doc.vue?raw`
  )
  sourceCode.value = code.default
}

onMounted(() => {
  getSourceCode()
})
</script>
<style lang="scss">
.preview-code {
  height: 300px;
  .showCode {
    cursor: pointer;
    color: #777;
    text-align: right;
    padding-right: 50px;
    &:hover {
      color: #409eff;
    }
  }
}

.v-enter-active,
.v-leave-active {
  transition: all 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
  transform: translateY(200px);
}
</style>

 组件出口

// index.ts
import ElTablePagination from './ElTablePagination/ElTablePagination.vue'
import SelectTree from './SelectTree/SelectTree.vue'
import Demo from './demo/Demo.vue'

// 按需引入
export { ElTablePagination, SelectTree, Demo }

const component = [ElTablePagination, SelectTree, Demo]

const HasakiUI = {
  install(App: any) {
    component.forEach((item) => {
      App.component(item.name, item)
    })
  }
}

export default HasakiUI

在src的main.ts中引入组件库

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import HasakiUI from './components/hasaki-ui'

import hljs from 'highlight.js' // 引入代码高亮文件
import 'highlight.js/styles/color-brewer.css'

const app = createApp(App)

app.directive('highlight', function (el) {
  const blocks = el.querySelectorAll('pre code')
  blocks.forEach((block) => {
    hljs.highlightBlock(block)
  })
})

app.use(router)
app.use(HasakiUI)
app.mount('#app')

接下来我们简单写下项目外壳样式

// views/home-page.vue
<template>
  <div class="home-page">
    <div class="home-page-sidebar">
      <div class="logo">
        <img src="/favicon.ico" alt="" />
        hasaki-ui
      </div>
      <ul v-for="item in routes" :key="item">
        <li
          v-for="(ele, index) in item.children"
          :key="ele"
          :class="{ active: mIndex == index }"
          @click="goPath(ele, index)"
        >
          {{ ele.name }}
        </li>
      </ul>
    </div>
    <main class="home-page-main">
      <router-view></router-view>
    </main>
  </div>
</template>

<script setup>
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const mIndex = ref(sessionStorage.getItem('mIndex') || '0')
const routes = computed(() => router.options.routes)

const goPath = (ele, index) => {
  mIndex.value = index
  router.push({
    path: ele.path
  })
  sessionStorage.setItem('mIndex', index)
}
</script>

<style lang="scss" scoped>
.home-page {
  display: flex;
  justify-content: space-between;
  width: 100%;
  height: 100%;
  overflow: hidden;
  &-sidebar {
    width: 200px;
    height: 100%;
    border-right: 1px solid #eee;
    text-align: center;
    .logo {
      display: flex;
      align-items: center;
      padding: 15px;
      font-size: 20px;
      img {
        width: 25px;
        margin-right: 10px;
      }
    }
    ul {
      li {
        height: 50px;
        line-height: 50px;
        cursor: pointer;
      }
      .active {
        color: #409eff;
        background-color: #ecf5ff;
        border-right: 1px solid #409eff;
      }
    }
  }
  &-main {
    flex: 1;
    padding: 20px 50px;
    overflow-y: auto;
  }
}
</style>

配置路由

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
  {
    path: '/',
    name: '组件页面',
    component: () => import('@/views/home-page.vue'),
    redirect: '/demo',
    children: [
      {
        path: '/demo',
        name: 'Demo',
        // @ts-ignore
        component: () => import('@/components/hasaki-ui/demo/doc/doc.md')
      },
      {
        path: '/el-table-pagination',
        name: '分页表格',
        // @ts-ignore
        component: () => import('@/components/hasaki-ui/ElTablePagination/doc/doc.md')
      },
      {
        path: '/select-tree',
        name: '树选择器',
        // @ts-ignore
        component: () => import('@/components/hasaki-ui/SelectTree/doc/doc.md')
      }
    ]
  }
]
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

export default router
// App.vue
<script setup lang="ts"></script>
<template>
  <div class="main">
    <RouterView />
  </div>
</template>
<style scoped lang="scss">
.main {
  width: 100vw;
  height: 100vh;
}
</style>

Markdown 插件需要我们在vite.config.ts中进行配置

到这里我们的一个简单的组件库demo就完成了

三、库模式开发打包配置 

        在此我们介绍如何把我们开发的组件库进行打包,重点是配置lib字段来进行库开发

// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import Markdown from 'vite-plugin-vue-markdown'

// https://vitejs.dev/config/
export default defineConfig({
  base: '/hasaki-ui/',
  plugins: [
    vue({
      include: [/\.vue$/, /\.md$/]
    }),
    vueJsx(),
    Markdown()
  ],
  build: {
    outDir: 'hasaki-ui', //输出文件名称
    lib: {
      entry: fileURLToPath(new URL('./src/components/hasaki-ui/index.ts', import.meta.url)), //指定组件编译入口文件
      name: 'hasaki-ui', // 包名
      fileName: 'hasaki-ui' // 打包文件名
    }, //库编译模式配置
    rollupOptions: {
      // 确保外部化处理那些你不想打包进库的依赖
      external: ['vue', 'vue-router'],
      output: {
        // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
        globals: {
          vue: 'Vue'
        }
      }
    }, // rollup打包配置
    terserOptions: {
      // 在打包代码时移除 console、debugger 和 注释
      compress: {
        /* (default: false) -- Pass true to discard calls to console.* functions.
          If you wish to drop a specific function call such as console.info and/or
          retain side effects from function arguments after dropping the function
          call then use pure_funcs instead
        */
        drop_console: true, // 生产环境时移除console
        drop_debugger: true // 生产环境时移除debugger
      },
      format: {
        comments: false // 删除注释comments
      }
    }
  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

在package.json中配置发布信息

{
  "name": "hasaki-ui", // 发布包的名称
  "version": "0.0.5",  // 版本号,每次更新注意升级版本号
  "private": false, // 是否为私有,这里注意应为false
  "main": "./hasaki-ui/hasaki-ui.umd.js", // 入口文件
  "module": "./hasaki-ui/hasaki-ui.mjs", // 模块入口
  "sideEffects": false,
  "keywords": [
    "hasaki",
    "hasaki-ui",
    "HasakiUI"
  ], // 搜索关键词
  "description": "一款基于Element-Plus开发的UI组件库,适用于vue3,大多为PC端后台管理系统常用组件", // 描述
  "author": "YXX", // 作者
  "exports": {
    "./hasaki-ui/style.css": "./hasaki-ui/style.css",
    ".": {
      "import": "./hasaki-ui/hasaki-ui.mjs",
      "require": "./hasaki-ui/hasaki-ui.umd.js"
    }
  }, // 引用路径映射,解决加载样式失败问题
  "files": [
    "hasaki-ui/*"
  ], // 描述了将软件包作为依赖项安装时要包括的条目,默认值为[“*”],这意味着它将包括所有文件,我们指定我们自己组件文件夹即可
 // ..重要的就上面这些
}

执行 npm/yarn/pnpm run build进行打包,出现如下表示打包成功了

四、发布

这一步其实很简单,一个命令搞定,npm publish,但是初次发布需要登录你的npm:

1、执行npm login 命令,输入用户名和密码,输入密码时是看不到的,之后按提示输入绑定的邮箱,成功后你的邮箱会收到一个one-time password,填入后即登录成功。

2、登录之后,就可以执行npm publish进行发布。

3、发布前要注意查看你的npm源是否为官方源https://registry.npmjs.org ,如果不是要切回官方源,否则发布失败

4、发布成功后,到npm上就可以看到我们的包了,至此我们就有了自己的一个开源包。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53058.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FitBot-一款先进的以健康为中心的聊天机器人

在健康意识高涨&#xff0c;追求均衡生活方式成为普遍追求的时代&#xff0c;营养问题无疑是核心支柱。然而&#xff0c;饮食计划的复杂性和大量的营养数据往往成为我们实现这种平衡的障碍。例如糖尿病患者&#xff0c;他们需要持续和准确的营养指导来有效管理血糖水平。如果能…

框架的知识点整理

目录 1、什么是Spring框架&#xff1f;Spring框架有哪些主要模块&#xff1f; 2 、 使用Spring框架有什么好处&#xff1f; 3、Spring MVC 工作原理 1、什么是Spring框架&#xff1f;Spring框架有哪些主要模块&#xff1f; Spring框架是一个开源的轻量级的Java应用程序开…

Spring事务创建与使用

目录 前言Spring中事务的实现声明式事务Transactional 作⽤范围Transactional 参数说明对于事务不回滚的解决方案 前言 在数据库中我们提到了 事务, 事务的定义为, 将一系列操作封装成一个整体去调用 , 要么一起成功, 要么一起失败 Spring中事务的实现 在Spring中事务的操作…

电动汽车市场的减速,正在让小鹏汽车付出代价

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结&#xff1a; &#xff08;1&#xff09;由于价格压力上升、竞争加剧和需求减弱&#xff0c;小鹏汽车的交付量出现了明显下滑&#xff0c;6月份的交付量已经同比下降了43%。 &#xff08;2&#xff09;小鹏汽车对2023年…

服务调用---------Ribbon和Feign

目录​​​​​​​ 1、Ribbon 1.1 Ribbon简介 1.2 Ribbon负载均衡 负载均衡原理 负载均衡策略 Ribbon和Nginx的区别 1.3 服务调用和Ribbon负载均衡实现 2、Feign&openFeign 3、Feign支持的配置 日志功能 连接池 feign-api远程包 1、Ribbon 1.1 Ribbon简介 Ribb…

【Vue3+Ts+Vite】配置滚动条样式

一、先看效果 二、直接上代码 <template><div class"main-container"><h1 v-for"index in 50" :key"index">这是home页面</h1></div> </template> <style lang"scss" scoped> .main-conta…

【AI底层逻辑】——篇章5(下):机器学习算法之聚类降维时间序列

续上&#xff1a; 目录 4、聚类 5、降维 6、时间序列 三、无完美算法 往期精彩&#xff1a; 4、聚类 聚类即把相似的东西归在一起&#xff0c;与分类不同的是&#xff0c;聚类要处理的是没有标签的数据集&#xff0c;它根据样本数据的分布特性自动进行归类。 人在认知是…

Flutter 最佳实践和编码准则

Flutter 最佳实践和编码准则 视频 前言 最佳实践是一套既定的准则&#xff0c;可以提高代码质量、可读性和可靠性。它们确保遵循行业标准&#xff0c;鼓励一致性&#xff0c;并促进开发人员之间的合作。通过遵循最佳实践&#xff0c;代码变得更容易理解、修改和调试&#xff…

基于光子实验的指数级加速的量子同态加密理论

前言 量子计算机不仅有望在某些重要任务上超越经典计算机&#xff0c;而且还能保护计算的隐私。例如&#xff0c;盲量子计算协议支持安全委托量子计算&#xff0c;其中客户端可以保护其数据和算法的隐私&#xff0c;不受分配来运行计算的量子服务器的影响。然而&#xff0c;这…

​《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(9)-Fiddler如何设置捕获Https会话​

1.简介 由于近几年来各大网站越来越注重安全性都改成了https协议&#xff0c;不像前十几年前直接是http协议直接裸奔在互联网。还有的小伙伴或者童鞋们按照上一篇宏哥的配置都配置好了&#xff0c;想大展身手抓一下百度的包&#xff0c;结果一试傻眼了&#xff0c;竟然毛都没有…

Selenium+Java环境搭建(测试系列6)

目录 前言&#xff1a; 1.浏览器 1.1下载Chrome浏览器 1.2查看Chrome浏览器版本 1.3下载Chrome浏览器的驱动 2.配置系统环境变量path 3.验证是否成功 4.出现的问题 结束语&#xff1a; 前言&#xff1a; 这节中小编给大家讲解一下有关于Selenium Java环境的搭建&…

数据结构: 线性表(顺序表实现)

文章目录 1. 线性表的定义2. 线性表的顺序表示:顺序表2.1 概念及结构2.2 接口实现2.2.1 顺序表初始化 (SeqListInit)2.2.2 顺序表尾插 (SeqListPushBack)2.2.3 顺序表打印 (SeqListPrint)2.2.6 顺序表销毁 (SeqListDestroy)2.2.5 顺序表尾删 (SeqListPopBack)2.2.6 顺序表头插 …

晋级榜单揭晓!华秋第九届硬创大赛-华南分赛区路演成功举办

7月21日&#xff0c;第十五届深创赛福田预选赛区暨华秋第九届硬创大赛华南分赛区决赛路演活动在深圳华强科创广场成功举办。活动由深圳华秋电子有限公司&#xff08;以下简称 华秋 &#xff09;、深圳市福田区新一代信息技术产业链党委、深圳新一代产业园、微纳研究院、华强科创…

【嵌入式学习笔记】嵌入式入门1——GPIO

1.什么是GPIO General Purpose Input Output&#xff0c;即通用输入输出端口&#xff0c;简称GPIO&#xff0c;作用是负责采集外部器件的信息或者控制外部器件工作&#xff0c;即输入输出。 2.STM32 GPIO简介 2.1.GPIO特点 不同型号&#xff0c;IO口数量可能不一样&#x…

中小学分班查询系统0成本制作方法公布了,人人可用

传统的学生分班查询平台通常需要进行专业的技术开发&#xff0c;以实现学生查询和查看分班信息的功能。这个过程涉及到软件开发、数据库设计、系统集成等多个环节&#xff0c;需要有一支专业的技术团队来完成。 然而&#xff0c;这样的技术开发和维护过程需要耗费大量的经济成…

HBase有写入数据,页面端显示无数据量

写了一个测试类&#xff0c;插入几条数据&#xff0c;测试HBase的数据量。很简单的功能&#xff0c;这就出现问题了。。网页端可以看到&#xff0c;能够看到读写请求&#xff0c;但是不管是内存、还是磁盘&#xff0c;都没有数据。 于是就想到去HDFS查看&#xff0c;也是有数据…

Python简要复习

Python程序设计复习 Python基础知识 python的特点 兼具编译型和解释型特性&#xff0c;兼顾过程式、函数式和面向对象编程范式的通用编程语言 解释型语言无需像编译型需要一次性的编译成机器码&#xff0c;然后运行&#xff0c;而是由名叫解释器的程序动态的将源代码逐句转…

热备份路由协议原理

热备份路由协议原理 HSRP协议/VRRP协议热备份协议 热备份协议&#xff08;Hot Standby Protocol&#xff09; 是一种基于冗余设计的协议&#xff0c;用于提高网络的可靠性和冗余性。它允许多个设备共享同一个IP地址&#xff0c;其中一个设备被选为主设备&#xff0c;其他设备…

Tinkercad 建模21个小技巧

21个Tinkercad 建模小技巧 原文 参考文章&#xff1a;在 Tinkercad 中加快设计的 22 个技巧 一起来了解一下21个Tinkercad 3D建模小技巧&#xff0c;让你快人一步。 技巧1 Copy & Paste 文件&#xff0c;整合设计 想把文件A里面的模型拷贝到文件B里面&#xff1f; 很容…

【Linux】Centos的一些快捷操作

Centos的一些快捷操作 一个窗口多个终端GVIM 一个窗口多个文件 一个窗口多个终端 GVIM 一个窗口多个文件