支持vue3/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件
/ 15 min read
Table of Contents
前言
在目前,Vue3 和其生态系统中的新星 Nuxt3 已成为许多开发者手中的利器,它们不仅带来了性能的飞跃,还极大地优化了开发体验。
我们都知道市场上较流行的组件库都支持全局自动导入和按需加载,但是我们思考一下,它们是怎么做的呢?
在这篇实战导向的指南中,我们将携手打造一个既高效又灵活的开源组件库插件,使其能在 Vue3 与 Nuxt3 项目中轻松地实现上述功能。
另:当前实战指南是基于这篇专栏《Headless UI 无头组件库的介绍与实现》来实现的,所以大家可以按顺序看看前面的文章,这样才事半功倍。
介绍
我们知道,在实际项目中,导入一个组件库一般分为两种,一种是单个文件手动导入,一种是全局自动导入
单个文件手动导入:一般就是在项目某个文件中直接导入,例如导入 Alert 组件;
import { Alert } from "@yi-ui/vue"全局自动导入:一般就是在配置文件中导入一次即可,例如在 vite.config.ts 或者 nuxt.config.ts 中,具体实现与项目使用的框架有强关联关系,同时全局导入也有很明显的缺点,下面我们会讲到。
所以我们思考一下,如果是你在实现一个组件库,应该怎么样才能无论是在 Vue3 项目还是在 Nuxt3 项目中,在导入组件的时候支持全局自动导入和按需加载的功能呢?
在此之前,我们需要先分析一下,他们的各自的优缺点,再结合相关分析去实现咱们的下一步计划;
一、单文件 import 手动导入
优点:
- 灵活性高:可以根据需要在各个页面或组件中按需导入具体需要的组件,避免不必要的资源加载。
- 易于控制版本:对于特定页面或功能,可以精确控制组件的版本和更新时机。
- 明确的依赖关系:代码中显式声明了组件的使用,便于理解和维护。
缺点:
- 重复导入:如果多个地方使用同一个组件,可能会导致代码中存在多处重复的导入语句。
- 配置繁琐:每次使用新组件都需要手动导入,增加了开发的繁琐度。
- 可能影响性能:如果不采用按需加载策略,一次性导入大量组件可能会增加初始加载时间。
以 element-plus 组件为例
a.vue
<template> <el-button>我是一个按钮a</el-button></template><script lang="ts" setup>import { ElButton } from 'element-plus'</script>b.vue
<template> <el-button>我是一个按钮b</el-button></template><script lang="ts" setup>import { ElButton } from 'element-plus'</script>这样不同的页面,不同的组件就需要手动导入。
二、全局自动导入
优点:
- 全局可用:通过这种方式注册的组件可以在项目的所有Vue组件中直接使用,无需每次单独导入。
- 配置集中:便于统一管理组件的全局配置,使得项目结构更加清晰。
- 易于维护:对于全局使用的组件,修改配置只需在一个地方进行,提高了维护效率。
缺点:
- 资源加载:全局注册的组件即使在某些页面不需要也会被加载,可能导致应用体积增大。
- 潜在冲突:全局组件可能与项目中其他组件或第三方库产生命名冲突。
- 降低灵活性:对于只想在特定页面或组件中使用的功能,全局注册可能不够灵活。
以 element-plus 组件为例
在 vue3 项目中使用:
import { createApp } from 'vue'import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)app.mount('#app')在 nuxt3 项目中使用:
export default defineNuxtConfig({ modules: [ '@element-plus/nuxt', ],})很明显,虽然解决手动导入的缺点,但是也丧失了手动导入的优点,那就是按需加载,减少包体积。
所以这时候我们就有了下面的方案
三、终极解决方案:全局自动导入+按需加载
正是因为上面两种方案的不完整性,我们想要一个既要全局可用,又要不加载全部的方法,那么咱们应该怎么做呢,很明显,我们的按需加载就被引入进来了;
以二者结合来达到最优解;
那么如何我们要需要做呢?
这时候就不得不引入神器:unplugin-vue-components 和 unplugin-auto-import 了;
但是我们文章的重点不在于教你如何去使用这两个插件,而是如何让你的组件库去支持全局导入+按需加载这两个功能。
所以接下来我们就着重的讲解一下在开源组件库中的支持,等支持了,咱们再教教如何使用!
四、研发组件库支持插件
新建 plugins 子包
cd packages
mkdir plugins
cd plugins1. 新建两个子包:
mkdir nuxt:支持 nuxt 的全局引入和按需加载mkdir resolver:支持 vite 版本的全局引入和按需加载
2. 配置 pnpm-workspace.yaml
packages: - packages/plugins/*3. 进入 nuxt 和 resolver 目录初始化:
pnpm init支持 Nuxt
其实所谓的支持 Nuxt,也就是写一个对应的 Nuxt 插件模块,所以也就教大家去写一个支持当前组件库的 Nuxt 模块。
考虑到文章的主题,这里不会很详细的教大家如何去写一个 Nuxt 模块,也不会详细的教大家各个 API 的功能点,只会大致粗略的讲解一下,要不然就又是一篇文章了。
1.分析
我们先简单分析一下写一个支持 Nuxt 模块需要几步?
- 第一步:创建一个 Nuxt 模块 name 为
@yi-ui/nuxt的模块; - 第二步:导入你的组件库
@yi-ui/vue中所有的组件; - 第三步:注册一个要自动导入的组件;
看到这,是不是觉得写一个 Nuxt 插件很简单,是的,的确很简单,只要你肯下功夫。
我们来实战:在 plugins/nuxt 新建 src/module.ts
2.安装
pnpm install @nuxt/kit @yi-ui/vue
pnpm install @nuxt/module-builder @nuxt/schema nuxt -D3.研发
import { addComponent, defineNuxtModule } from '@nuxt/kit'
const allComponents = { hoverCard: [ 'HoverCardRoot', 'HoverCardTrigger', 'HoverCardPortal', 'HoverCardContent', 'HoverCardArrow', ],}
export interface ModuleOptions { components: Partial<Record<keyof typeof allComponents, boolean>> | boolean prefix: string}
// 第一步:创建一个 Nuxt 模块 name 为 `@yi-ui/nuxt` 的模块;export default defineNuxtModule<ModuleOptions>({ meta: { name: '@yi-ui/nuxt', configKey: 'yi-ui', compatibility: { nuxt: '^3.0.0', }, }, defaults: { prefix: '', components: true, }, async setup(options, nuxt) {
// 第二步:导入你的组件库 `@yi-ui/vue` 中所有的组件; function getComponents() { if (typeof options.components === 'object') { return Object.entries(allComponents) .filter(([name]) => (options.components as Record<string, boolean>)[name]) .flatMap(([_, components]) => components) }
if (options.components) return Object.values(allComponents).flat()
return [] }
// 第三步:注册一个要自动导入的组件; for (const component of getComponents()) { addComponent({ name: `${options.prefix}${component}`, export: component, filePath: '@yi-ui/vue', }) } }})4.build & 运行
"scripts": { "build": "pnpm run dev:prepare && nuxt-module-build", "dev": "nuxi dev playground", "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"},其中 playground 模板直接看此处,就不详细介绍了
最后发包与前面的 @yi-ui/vue 方法一致,就不赘述了。
支持按需加载
1.分析
我们知道,vue 组件库的按需加载一般都是使用的 unplugin-vue-components 来实现,通过官方文档的介绍,如果你要想让你的组件库支持,那就必须拓展一个 unplugin-vue-components 的解析器;
例如那些使用较多的组件库,都有内置的解析器(下面是一些常见的组件库):
所以我们只需要实现我们组件库的解析器,那就可以支持按需加载啦 ~
开干~
2.新建 & 安装
# 新建文件mkdir packages/plugins/resolvers
# 初始化pnpm init安装:
pnpm install unplugin-vue-components
pnpm install tsup -D3. 研发
新建:在 plugins/resolvers 新建 src/index.ts
import { type ComponentResolver } from 'unplugin-vue-components'
const components = { hoverCard: [ 'HoverCardRoot', 'HoverCardTrigger', 'HoverCardPortal', 'HoverCardContent', 'HoverCardArrow', ],}
export interface ResolverOptions { /** * 模板中使用的组件的前缀 * * @defaultValue "" */ prefix?: string}
export default function (options: ResolverOptions = {}): ComponentResolver { const { prefix = '' } = options
return { type: 'component', resolve: (name: string) => { if (name.toLowerCase().startsWith(prefix.toLowerCase())) { const componentName = name.substring(prefix.length) if (Object.values(components).flat().includes(componentName)) { return { name: componentName, from: '@yi-ui/vue', } } } }, }}4. build
"scripts": { "dev": "pnpm run build --watch --ignore-watch examples", "build": "tsup src/index.ts --dts --format cjs,esm"}发包
1. 改造根目录 package.json 的命令:
"script": { ... "clear": "rimraf packages/**/dist packages/plugins/**/dist", "build": "pnpm run clear && pnpm run build:vue && pnpm run build:plugins", "build:vue": "pnpm -r --filter=./packages/vue run build", "build:plugins": "pnpm --filter=./packages/plugins/** --parallel run build", ...}2.发包执行顺序与前面的 @yi-ui/vue 一致
pnpm changeset
pnpm release更加详细的部分,会在下一章中讲解
到这里,你的组件库就已经可以支持全局自动导入和按需加载啦~
接下来就是要在你的实际项目中使用了
五、在 Vue3 中使用
安装
安装组件库:
pnpm i @yi-ui/vue
pnpm i @yi-ui/resolvers unplugin-vue-components unplugin-auto-import -D配置 vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'import Components from 'unplugin-vue-components/vite'import YiUIResolver from '@yi-ui/resolvers'
export default ({ mode }: { mode: string }) => { return defineConfig({ plugins: [ AutoImport({ resolvers: [YiUIResolver()], }), Components({ resolvers: [YiUIResolver()], }), ], })}使用
-
在你的 vue3 项目中使用
@yi-ui/vue中的组件,例如:HoverCard -
运行 pnpm dev 时,会自动生成
components.d.ts和auto-imports.d.ts
Tips:在你 pnpm dev 时,由于插件不知道你使用了哪个组件,所以在 components.d.ts 中不会生成对应的组件,导致部分组件缺失;
只有在你访问该页面时,插件就会检测到,会自动生成到 components.d.ts 中;
还有一种就是每次 pnpm build 时,都会完整的生成 components.d.ts;
所以建议你研发新项目时,先 build 一下,以免导致部分组件缺失。
六、Nuxt3 中使用
安装
安装组件库:
pnpm i @yi-ui/vue @yi-ui/nuxt配置 nuxt.config.ts
export default defineNuxtConfig({ modules: ['@yi-ui/nuxt']})使用
-
在你的 nuxt3 项目中直接使用
@yi-ui/vue中的组件,例如:HoverCard -
运行 pnpm dev 时,会在
.nuxt目录下自动生成components.d.ts
那么到这,你就可以在你的项目中无忧无虑的使用 @yi-ui/vue 组件了,基本上也是一个合格的组件库了。
总结
看到这,我相信你也应该能熟知个大半,其中一些细节和实现文章也都有介绍到,希望你能在实践的过程中慢慢品味。
最后,本篇文章也几乎是当前专栏无头组件库的实现的核心代码实践的最后一篇文章了,后续就是一些拓展了。
当然其中难免会有一些细节的疏忽,望海涵,大家如若有发现,可留言,作者后续会及时更新。
接下来的安排:
- 打包构建
- 版本发布
- 贡献指南
Headless UI 往期相关文章:
- 在 2023 年屌爆了一整年的 shadcn/ui 用的 Headless UI 到底是何方神圣?
- 实战开始 🚀 在 React 和 Vue3 中使用 Headless UI 无头组件库
- 无头组件库既然这么火 🔥 那么我们自己手动实现一个来完成所谓的 KPI 吧
- 泰裤辣 🚀 原来实现一个 Popover 无头组件比传统组件简单辣么多!!
- 手把手教你写一个 headless 无头组件的单元测试、集成测试、E2E 测试
- 用更合适的文档库!打造专属品质,尽显码农风采!
如果想跟我一起讨论技术吹水摸鱼, 欢迎加入前端学习群聊
如果想一起讨论技术:欢迎加入技术讨论群
如果想一起吹水摸鱼:欢迎加入吹水摸鱼群
如果想一起老司机吹水摸鱼:欢迎加入老司机吹水摸鱼群(懂得都懂)
如果扫码人数满了,可以扫码添加个人 vx 拉你:JeddyGong
感谢大家的支持,码字实在不易,其中如若有错误,望指出,如果您觉得文章不错,记得 点赞关注加收藏 哦 ~