likes
comments
collection
share

支持vue3/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件

作者站长头像
站长
· 阅读数 61

前言

在目前,Vue3 和其生态系统中的新星 Nuxt3 已成为许多开发者手中的利器,它们不仅带来了性能的飞跃,还极大地优化了开发体验。

我们都知道市场上较流行的组件库都支持全局自动导入按需加载,但是我们思考一下,它们是怎么做的呢?

在这篇实战导向的指南中,我们将携手打造一个既高效又灵活的开源组件库插件,使其能在 Vue3 与 Nuxt3 项目中轻松地实现上述功能。

介绍

我们知道,在实际项目中,导入一个组件库一般分为两种,一种是单个文件手动导入,一种是全局自动导入

单个文件手动导入:一般就是在项目某个文件中直接导入,例如导入 Alert 组件;

import { Alert } from "@yi-ui/vue"

全局自动导入:一般就是在配置文件中导入一次即可,例如在 vite.config.ts 或者 nuxt.config.ts 中,具体实现与项目使用的框架有强关联关系,同时全局导入也有很明显的缺点,下面我们会讲到。

所以我们思考一下,如果是你在实现一个组件库,应该怎么样才能无论是在 Vue3 项目还是在 Nuxt3 项目中,在导入组件的时候支持全局自动导入和按需加载的功能呢?

在此之前,我们需要先分析一下,他们的各自的优缺点,再结合相关分析去实现咱们的下一步计划;

一、单文件 import 手动导入

优点:

  1. 灵活性高:可以根据需要在各个页面或组件中按需导入具体需要的组件,避免不必要的资源加载。
  2. 易于控制版本:对于特定页面或功能,可以精确控制组件的版本和更新时机。
  3. 明确的依赖关系:代码中显式声明了组件的使用,便于理解和维护。

缺点:

  1. 重复导入:如果多个地方使用同一个组件,可能会导致代码中存在多处重复的导入语句。
  2. 配置繁琐:每次使用新组件都需要手动导入,增加了开发的繁琐度。
  3. 可能影响性能:如果不采用按需加载策略,一次性导入大量组件可能会增加初始加载时间。

以 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>

这样不同的页面,不同的组件就需要手动导入。

二、全局自动导入

优点:

  1. 全局可用:通过这种方式注册的组件可以在项目的所有Vue组件中直接使用,无需每次单独导入。
  2. 配置集中:便于统一管理组件的全局配置,使得项目结构更加清晰。
  3. 易于维护:对于全局使用的组件,修改配置只需在一个地方进行,提高了维护效率。

缺点:

  1. 资源加载:全局注册的组件即使在某些页面不需要也会被加载,可能导致应用体积增大。
  2. 潜在冲突:全局组件可能与项目中其他组件或第三方库产生命名冲突。
  3. 降低灵活性:对于只想在特定页面或组件中使用的功能,全局注册可能不够灵活。

以 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-componentsunplugin-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 模板直接看此处,就不详细介绍了

支持vue3/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件

最后发包与前面的 @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/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件

更加详细的部分,会在下一章中讲解

到这里,你的组件库就已经可以支持全局自动导入按需加载啦~

接下来就是要在你的实际项目中使用了

五、在 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()],
      }),
    ],
  })
}

使用

  1. 在你的 vue3 项目中使用 @yi-ui/vue 中的组件,例如:HoverCard

  2. 运行 pnpm dev 时,会自动生成 components.d.tsauto-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']
})

使用

  1. 在你的 nuxt3 项目中直接使用 @yi-ui/vue 中的组件,例如:HoverCard

  2. 运行 pnpm dev 时,会在 .nuxt 目录下自动生成 components.d.ts


那么到这,你就可以在你的项目中无忧无虑的使用 @yi-ui/vue 组件了,基本上也是一个合格的组件库了。

总结

看到这,我相信你也应该能熟知个大半,其中一些细节和实现文章也都有介绍到,希望你能在实践的过程中慢慢品味。

当然其中难免会有一些细节的疏忽,望海涵,大家如若有发现,可留言,作者后续会及时更新。

接下来的安排:

  • 打包构建
  • 版本发布
  • 贡献指南

Headless UI 往期相关文章:

支持vue3/nuxt3的开源组件库,如何研发全局自动导入和按需加载插件

如果想一起讨论技术:欢迎加入技术讨论群

如果想一起吹水摸鱼:欢迎加入吹水摸鱼群

如果想一起老司机吹水摸鱼:欢迎加入老司机吹水摸鱼群(懂得都懂)

感谢大家的支持,码字实在不易,其中如若有错误,望指出,如果您觉得文章不错,记得 点赞关注加收藏 哦 ~

转载自:https://juejin.cn/post/7367923380345651237
评论
请登录