一、组件库的常规引入方式
1、全量引入
在入口文件处一次性注册所有组件,并引入所有的样式文件。这样做的问题是打包产物体积较大,存在大量冗余代码,影响页面加载速度,对于有包体积限制的小程序开发而言更是无法接受的。
import NutUI from '@nutui/nutui'
import '@nutui/nutui/dist/style.css'
...
app.use(NutUI)
2、按需引入
只将用到的组件及其样式文件引入,减小项目体积。这种方式下,组件库需要分别考虑组件代码与样式代码的按需引入方式。
- 组件代码:以库模式打包输出 ESM 模块的组件,通过构建工具默认提供的 TreeShaking 功能就能实现组件代码的按需引入,无需其他配置。
- 样式代码:样式则需要单独引入对应组件的样式代码
此时使用一个组件时要这样写:
import { Button } from '@nutui/nutui'
import '@nutui/nutui/dist/packages/button/style'
...
app.use(Button)
显然,这里样式的引入耗时费力,我们可以借助一些插件来自动化这一过程,比如 Babel 插件 babel-import-plugin
或者是 Vite 生态下的 vite-plugin-style-import
插件。
它们的作用如下:
import { Button } from '@nutui/nutui'
↓ ↓ ↓ ↓ ↓ ↓
import { Button } from '@nutui/nutui'
import '@nutui/nutui/dist/packages/button/style'
这是 NutUI 3.x 版本采用的按需引入方式,是当前业界主流组件库默认推荐的方式。但这种方式依然存在一些可以优化的地方:
- 需要在入口文件中维护引入的所有组件名,注册组件与使用组件难以做到同步。
- 通用性不强。前者依赖于 Babel 工具链,无法在 SWC 项目中使用,后者则局限于 Vite 框架。在不同的项目中需要使用不同的插件,开发者面对的是较为繁琐的快速上手文档,同时从组件库建设的角度看需要同时维护多份配置方案,负担较重。
二、自动按需引入插件
1、它能做什么?
这个插件能帮忙开发者实现自动按需引入功能,即开发者只需要在 template 模板中使用组件 <nut-button >
标签,插件会在项目构建时自动将对应的组件引入和组件样式引入语句添加进代码里,不再需要手动引入和注册组件。
// 直接使用
<template>
<nut-button></nut-button>
</template>;
// 插件会自动在文件中添加下面的代码
import { Button } from "@nutui/nutui";
import "@nutui/nutui/dist/packages/button/style";
更重要的是,它基于 unplugin 插件,一个同时支持 Rollup、Vite、Webpack、esbuild 甚至是 Rspack 等构建工具的统一插件方案。
这意味着,不管开发者使用 NutUI 进行 H5 还是 Taro 多端开发,或者使用 Vite、vue-cli、webpack 创建项目,都可以使用同一个插件,设置相同的配置选项。在未来 Taro 发布 Vite 版本时,NutUI 也能在第一时间做到无缝兼容。
2、它的实现原理是什么?
核心思想很简单:
在构建工具将组件库中的 Vue SFC 或者 JSX 文件转换为 JS 文件时,会通过 resolveComponent
方法注册组件,unplugin-vue-components
会在这时匹配文件中的这些注册语句,收集用到的组件名称,与组件库的配置函数进行匹配,命中后就会导入对应的组件并注册,同时引入目标组件的副作用代码。
3、如何编写配置函数?
第一步:
组件库应采用统一的组件命名风格,便于后续输出组件映射关系,比如 NutUI 使用的 Vue 推荐命名风格:
// 组件名:CamelCase
import { ConfigProvider } from '@nutui/nutui'
// 组件注册(同时适配了 app.use 方式)
Vue.component('nut-config-provider', ConfigProvider)
// 组件使用名称:带有组件库标识的 spinal-case 标签
<template>
<nut-config-provider></nut-config-provider>
</template>
第二步:编写插件配置函数 resolver
插件配置函数需要返回一个包含 name
、from
、sideEffects
三个字段的对象,它的输入则是在代码中检测到的目标组件名。
- 输入:template 中的组件名
nut-button
及其等效名称NutButton
- 输出 1:
name
导出的组件名Button
- 输出 2:
from
包名,也可以是路径名@nutui/nutui
或者@nutui/nutui-taro
- 输出 3:
sideEffects
需要引入的其他副作用代码,例如样式文件@nutui/nutui/dist/packages/button/style
于是一个最简版的 reolver 函数定义如下:
const NutUIResolver = () => {
return (name) => {
// nut-button / NutButton
if (name.startsWith("Nut")) {
const partialName = name.slice(3);
return {
name: partialName, // Button
from: "@nutui/nutui",
sideEffects: `@nutui/nutui/dist/packages/${partialName.toLowerCase()}/style`
};
}
};
};
三、再往前一步
从 unplugin-vue-components
插件的实现原理上看,有一些组件注定是无法支持的,即不需要在 template 中声明而直接调用的函数式组件。
在 NutUI 中有四个这样的组件:Toast
、Notify
、Dialog
和 ImagePreview
,它们都用 show + 组件名的形式作为一个函数从项目入口文件导出。
使用方式如下:
// Toast
import { showToast } from "@nutui/nutui";
import "@nutui/nutui/dist/packages/toast/style";
这样写有些麻烦了,有没有什么办法也能实现这些组件的自动引入呢?
这就要使用到 unplugin-auto-import
插件了,unplugin-vue-components
插件只是前者在引入 Vue 组件时的一个特殊实现,而它的功能则更强大。
比如在使用 Vue Composition API 或者 React Hooks 时,几乎每个文件内都要引入 ref
、reactive
或者是 useState
、useEffect
方法。为了让开发者能少写这么一行代码,unplugin-auto-import
帮你做了,它支持配置常用的方法,只需要提供变量名与包名,一旦匹配到了这些内容,在构建时它会自动帮你添加到代码里。
回到我们的目标上来,把 NutUI 组件库中 showToast
这类函数式组件方法放入前面定义的 resolver 函数中,对这类组件名进行标记,一旦识别到了这些名称,就自动生成对应的组件和样式引入代码,这样就实现了所有组件的自动且按需引入功能。
最终完整的配置使用方式也非常简单:
// 引入插件
import a from "unplugin-vue-components/xxx";
import b from "unplugin-auto-import/xxx";
// 从 NutUI 组件库中引入配置函数
import Resolver from "@nutui/nutui/dist/resolver";
// 配置使用
export default {
plugin: [a({ resolvers: [Resolver()] }), b({ resolvers: [Resolver()] })]
};
四、注意事项
1、省去了手动导入组件的步骤,但是类型提示也消失了?
别急,插件已经帮你考虑好了。在默认配置下,它会将自动引入的组件的类型声明放入 Vue 全局组件声明中,生成一个 components.d.ts
文件,而且每次运行代码都会自动更新该文件。只需在 TypeScript 配置中 include 该文件,类型提示就出现了。
// components.d.ts
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
NutButton: typeof import('@nutui/nutui')['Button']
NutCell: typeof import('@nutui/nutui')['Cell']
……
}
}