这是我参与更文挑战的第3天,活动详情查看 更文挑战
按需加载最早的使用方式
antd按需加载
插件配置
在vite.config.ts
import usePluginImport from "vite-plugin-importer";
export default defineConfig({
plugins: [
usePluginImport({
libraryName: "ant-design-vue",
libraryDirectory: "es",
style: "css",
}),
]
})
使用
import { Button, List, Checkbox, Popconfirm, Input } from "ant-design-vue";
components: {
[Checkbox.name]: Checkbox,
[Input.name]: Input,
[List.name]: List,
[List.Item.name]: List.Item,
AListItemMeta: List.Item.Meta, //这里用框架List.Item.Meta.name注册不上只能写死,可能使用displayName可以
[Button.name]: Button,
[Popconfirm.name]: Popconfirm,
},
痛点:
- 由于antd组件库使用了很多子组件,比如
List
下的组件ListItem
,如果少注册了一个都会造成模板解析不了 - 需要引入一遍,然后注册时候
key
写一遍,值又写一遍,同样的代码需要写3遍,并要关注子组件和父组件的关系 - 部分组件并未提供
name
属性,比如AListItemMeta
所以需要写死,导致风格不一致
element-plus按需加载
插件配置
在vite.config.ts
import styleImport from "vite-plugin-style-import";
export default defineConfig({
plugins: [
styleImport({
libs: [
{
libraryName: "element-plus",
esModule: true,
ensureStyleFile: true,
resolveStyle: name => {
return `element-plus/lib/theme-chalk/${name}.css`;
},
resolveComponent: name => {
return `element-plus/lib/${name}`;
},
},
],
}),
]
})
使用方式
import { ElForm, ElButton, ElFormItem, ElInput } from "element-plus";
components: {
ElForm,
ElButton,
ElInput,
ElFormItem,
},
痛点:
- 同样是父子组件,要引入两遍,比如
Form
和FormItem
,与antd
不同的是element
需要分别引入父子组件,并且只能使用components
的注册方式,但是antd
除此还支持插件方式注册,用app.use(xxx)
改进
为了解决antd父子组件引入的父子组件造成困扰,antd
是提供app.use(xx)
这种插件注册的方式,antd
内部自动解决了子组件依赖的问题,比如要使用List组件,只需要app.use(List)
即可使用List和ListItem,ListItemMeta
,这样方便了不少
于是写了一个useComp
的方法使用 app.use(comp);
进行注册
在vue3中首先要获取到app实例,vue3提供了getCurrentInstance
这个方法可以获取到当前组件的实例,然后在通过当前实例获取到全局上下文中的app对象,instance?.appContext.app;
,这样就可以使用app.use进行注册了,还要注意的是,同一个组件避免重复注册,需要记录一下已经注册过的组件
代码useAntd.ts如下:
import { Plugin, getCurrentInstance } from "vue";
interface Registed {
[key: string]: boolean;
}
let registed: Registed = {};
type Comp = {
displayName?: string;
name?: string;
} & Plugin;
type RegisteComps = (...comps: Comp[]) => void;
export const useComp: RegisteComps = (...comps) => {
comps.forEach(comp => {
const name = comp.displayName || comp.name;
if (name && !registed[name]) {
const instance = getCurrentInstance();
const app = instance?.appContext.app;
if (app) {
app.use(comp);
registed[name] = true;
}
}
});
};
使用方式:
import { List, Table, Button, Space } from "ant-design-vue";
import { useComp } from "@/hooks/useAntd";
//...略
setup() {
useComp(List, Table, Button, Space);
return {}
}
解决痛点:
- 无需关系父子组件依赖关系
- 减少
components
注册的代码 - 用到哪些组件,直接将
import
括号中的组件名称,复制粘贴到useComp
方法中即可,属于无脑操作,
遗留痛点:
element,naive
还是需要用components
进行一一注册- 相较tsx在setup中还是多了一行注册的代码
理想形态:
所用组件无需关心引入和注册,就像全量引入一样,直接在模板使用即可,既可以写起来舒服,又无需关注忘记注册组件带来的烦恼
长时间一直在寻找类似的方法,直到逛社区,发现大佬antfu开源了一个叫vite-plugin-components
的插件,这正是我想要寻找的。
它实现的功能就是:自动解析模板中所用到的组件,然后自动按需引入,再也不需要手动进行注册了。
但是理想很丰满,现实很骨感,踩到了一些坑
插件配置:
import ViteComponents, {
AntDesignVueResolver,
ElementPlusResolver,
ImportInfo,
kebabCase,
NaiveUiResolver,
pascalCase,
} from "vite-plugin-components";
export default defineConfig({
plugins: [
ViteComponents({
customComponentResolvers: [
AntDesignVueResolver(),//官方插件提供
ElementPlusResolver(),//官方插件提供
NaiveUiResolver(),//官方插件提供
]
})
]
})
尝试结果:
- naiveui完美支持,赞!
- elementui官方用的scss,需要安装scss依赖才行
- antdv 只有少部分组件可以解析,像
layout,list table
这种常用组件不能解析
解决:
element-plus重写resolver:
重写的理由:由于项目没有使用scss只用了less,所以把scss转成css加载样式的方式
官方的写法如下:
const { importStyle = true } = options
if (name.startsWith('El')) {
const partialName = name[2].toLowerCase() + name.substring(3).replace(/[A-Z]/g, l => `-${l.toLowerCase()}`)
return {
path: `element-plus/es/el-${partialName}`,
sideEffects: importStyle ? `element-plus/packages/theme-chalk/src/${partialName}.scss` : undefined,
}
}
重写之后:改为从lib目录引入的路径,直接使用组件名称作为文件目录名引入,不在做复杂的组件名转换了。 代码如下:
customComponentResolvers: [
// AntDesignVueResolver(),
// ElementPlusResolver(),
NaiveUiResolver(),
name => {
if (name.startsWith("El")) {
// Element UI
const partialName = kebabCase(name); //ElButton->el-button
return {
path: `element-plus/lib/${partialName}`,
sideEffects: `element-plus/lib/theme-chalk/${partialName}.css`,
};
}
}
]
antdv
官方的做法是:
export const AntDesignVueResolver = (): ComponentResolver => (name: string) => {
if (name.match(/^A[A-Z]/))
return { importName: name.slice(1), path: 'ant-design-vue/es' }
}
存在的问题:
-
<a-list-item>
这种组件就没法到ant-design-vue/es/list-item
这个目录找,并不存在这样的目录,实际上他的真实目录是ant-design-vue/es/list/Item.js
-
<a-layout-content
组件也没有对应的路径,他是通过layout中的生成器generator方法生成的,并绑定到layout对象上,他的实际路径应该是ant-design-vue/es/layout/layout.js
的Content属性 -
<a-select-option>
这个组件也是绑定到select上的,但是实际上他引入是引入的vc-select/Option.js
属于基层组件 -
<a-menu-item>
组件是属于menu的子组件,目录是ant-design-vue/es/menu/MenuItem.js
,这个和之前规则不一样,我以为应该叫Item才对,但是这里去不同,所以需要特殊处理, -
还有
<a-tab-pane>
这种组件,他所需要的样式目录是在tabs,但是实际上它的文件目录是在vc-tabs/src
下,也需要特殊处理
以上问题都是官方的写法无法正常加载到对应组件的原因,因此为了解决以上问题,我针对不同的情况写了一大坨判断逻辑,来修正组件的引入路径,但是依旧有部分组件无法引入到,因为有些组件是functional的,或者是generator生成的,并不具备独立的子组件文件,暂时也没有找到合适的方法引入对应的子组件属性
解析组件路径的getCompPath
方法,代码如下:
function getCompPath(
compName: string
): {
dirName: string;
compName: string;
styleDir: string;
importName?: string;
sideEffects?: ImportInfo;
} {
const hasSubComp = [
"Menu",
"Layout",
"Form",
"Table",
"Modal",
"Radio",
"Button",
"Checkbox",
"List",
"Collapse",
"Descriptions",
"Tabs",
"Mentions",
"Select",
"Anchor",
"Typography",
// "TreeSelect",
]; //包含子组件的组件
const keepSelf = [
"MenuItem",
"SubMenu",
"FormItem",
"RadioButton",
"CollapsePanel",
"TabPane",
"AnchorLink",
]; //保留原子组件名称
const keepFather = [
"LayoutHeader",
"LayoutContent",
"LayoutFooter",
"DescriptionsItem",
]; //需要使用父组件名称的子组件 LayoutFooter->'' 之所以转成空是因为最后拼接的结果是dirName+compName,避免重复
const rootName = hasSubComp.find((name: string) => compName.startsWith(name));
const usePrevLevelName = ["ListItemMeta"]; //使用当前组件的上一级名称 ListItemMeta->Item
const getPrevLevelName = () => {
const split = kebabCase(compName).split("-");
return pascalCase(split[split.length - 2]);
};
const fatherAlias = {
TabPane: "vc-tabs/src",
MentionsOption: "vc-mentions/src",
SelectOption: "vc-select",
TreeSelectNode: "vc-tree-select/src",
};
const compAlias = {
TreeSelectNode: "SelectNode",
};
const styleMap = {
TabPane: "tabs",
MentionsOption: "mentions",
SelectOption: "select",
TreeSelectNode: "tree-select",
};
// const importNameMap = {
// LayoutContent: "Content",
// LayoutHeader: "Header",
// LayoutFooter: "Footer",
// };
let dirName = rootName?.toLowerCase() ?? kebabCase(compName);
if (fatherAlias[compName]) {
dirName = fatherAlias[compName];
}
let compNameStr = "";
if (keepSelf.includes(compName)) {
compNameStr = compName;
} else if (keepFather.includes(compName)) {
compNameStr = "";
} else if (usePrevLevelName.includes(compName)) {
compNameStr = getPrevLevelName();
} else if (rootName) {
compNameStr = compName.replace(rootName, "");
}
const compRequired = {
TypographyTitle: "ant-design-vue/es/" + dirName + "/Base",
TypographyText: "ant-design-vue/es/" + dirName + "/Base",
};
return {
// importName: importNameMap[compName],
dirName: fatherAlias[compName] ?? dirName,
styleDir: `${styleMap[compName] ?? dirName}`,
compName: compAlias[compName] ?? compNameStr,
sideEffects: compRequired[compName]
? {
path: compRequired[compName],
}
: undefined,
};
}
自定义resolver,代码如下
ViteComponents({
customComponentResolvers: [
name => {
if (name.match(/^A[A-Z]/)) {
//ant-design-vue
const importName = name.slice(1);
const dirName = kebabCase(importName);
const compName = pascalCase(importName); //AListItem->ListItem
const compPath = getCompPath(compName);//这里解析组件的真实路径
const sideEffects = [
{
path: `ant-design-vue/es/${compPath.styleDir}/style`,
},
];
if (compPath.sideEffects) {
sideEffects.push(compPath.sideEffects);
}
return {
path: `ant-design-vue/es/${compPath.dirName}/${compPath.compName}`,
sideEffects,
};
}
return null;
},
],
globalComponentsDeclaration: true,
}),
经过解析,绝大部分组件可以使用,还有遗留的部分组件不能正常使用
插件生成部分组件的声明文件如下:
declare module 'vue' {
export interface GlobalComponents {
AMenuItem: typeof import('ant-design-vue/es/menu/MenuItem')['default']
AMenu: typeof import('ant-design-vue/es/menu/')['default']
ALayoutHeader: typeof import('ant-design-vue/es/layout/')['default']
ALayoutContent: typeof import('ant-design-vue/es/layout/')['default']
ALayoutFooter: typeof import('ant-design-vue/es/layout/')['default']
ALayout: typeof import('ant-design-vue/es/layout/')['default']
AButton: typeof import('ant-design-vue/es/button/')['default']
ADivider: typeof import('ant-design-vue/es/divider/')['default']
AInput: typeof import('ant-design-vue/es/input/')['default']
AFormItem: typeof import('ant-design-vue/es/form/FormItem')['default']
ASpace: typeof import('ant-design-vue/es/space/')['default']
AForm: typeof import('ant-design-vue/es/form/')['default']
ACheckbox: typeof import('ant-design-vue/es/checkbox/')['default']
AListItemMeta: typeof import('ant-design-vue/es/list/Item')['default']
APopconfirm: typeof import('ant-design-vue/es/popconfirm/')['default']
AListItem: typeof import('ant-design-vue/es/list/Item')['default']
AList: typeof import('ant-design-vue/es/list/')['default']
ATable: typeof import('ant-design-vue/es/table/')['default']
}
}
具体问题如下:
layout
下的组件,Content,Header,Footer
,由于默认只引入default
导出的,如果引入类似['default']['Content']
,应该就可以正常,现在问题是把Content等子组件都当做了layout进行解析,导致样式会有问题- ListItemMeta组件导入不正常,同样只能引入
['default']
,如果可以引入['default']['Meta']
应该就可以解决了 - Typography组件 引入title组件会报错:
TypeError: baseProps is not a function
但是实际上该组件使用相对路径引入了这个方法
希望大佬们可以帮忙一起看看上述问题该如何解决,感觉现在的代码写的有些麻烦,如果有更优雅的方法可以交流一下
相关issue和代码
antdv有关该问题的 issue
vite-plugin-components 有关该问题的issue
代码: github——todo项目地址
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!