支持vue3/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件
前言
在目前,Vue3 和其生态系统中的新星 Nuxt3 已成为许多开发者手中的利器,它们不仅带来了性能的飞跃,还极大地优化了开发体验。
我们都知道市场上较流行的组件库都支持全局自动导入和按需加载,但是我们思考一下,它们是怎么做的呢?
在这篇实战导向的指南中,我们将携手打造一个既高效又灵活的开源组件库插件,使其能在 Vue3 与 Nuxt3 项目中轻松地实现上述功能。
介绍
我们知道,在实际项目中,导入一个组件库一般分为两种,一种是单个文件手动导入,一种是全局自动导入
单个文件手动导入:一般就是在项目某个文件中直接导入,例如导入 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 项目中使用:
// main.ts
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 项目中使用:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@element-plus/nuxt',
],
})
很明显,虽然解决手动导入的缺点,但是也丧失了手动导入的优点,那就是按需加载,减少包体积。
所以这时候我们就有了下面的方案
三、终极解决方案:全局自动导入+按需加载
正是因为上面两种方案的不完整性,我们想要一个既要全局可用,又要不加载全部的方法,那么咱们应该怎么做呢,很明显,我们的按需加载就被引入进来了;
以二者结合来达到最优解;
那么如何我们要需要做呢?
这时候就不得不引入神器:unplugin-vue-components 和 unplugin-auto-import 了;
但是我们文章的重点不在于教你如何去使用这两个插件,而是如何让你的组件库去支持全局导入+按需加载这两个功能。
所以接下来我们就着重的讲解一下在开源组件库中的支持,等支持了,咱们再教教如何使用!
四、研发组件库支持插件
新建 plugins 子包
cd packages
mkdir plugins
cd plugins
1. 新建两个子包:
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 -D
3.研发
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 -D
3. 研发
新建:在 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
// 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 往期相关文章:
如果想一起讨论技术:欢迎加入技术讨论群
如果想一起吹水摸鱼:欢迎加入吹水摸鱼群
如果想一起老司机吹水摸鱼:欢迎加入老司机吹水摸鱼群(懂得都懂)
感谢大家的支持,码字实在不易,其中如若有错误,望指出,如果您觉得文章不错,记得 点赞关注加收藏
哦 ~
转载自:https://juejin.cn/post/7367923380345651237