Add auto import react and antd

This commit is contained in:
2023-09-01 22:39:19 +08:00
parent 7cedf04d92
commit e71dec551e
12 changed files with 526 additions and 78 deletions

View File

@@ -8,7 +8,8 @@ module.exports = {
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:react-hooks/recommended', 'plugin:react-hooks/recommended',
'plugin:prettier/recommended' 'plugin:prettier/recommended',
'./.eslintrc-auto-import.json'
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist-ssr
# Auto generated # Auto generated
/auto-imports.d.ts /auto-imports.d.ts
/.eslintrc-auto-import.json

421
build/resolvers/antd.ts Normal file
View File

@@ -0,0 +1,421 @@
export function kebabCase(key: string): string {
const result: string = key.replace(/([A-Z])/g, ' $1').trim()
return result.split(' ').join('-').toLowerCase()
}
export type Awaitable<T> = T | PromiseLike<T>
export interface ImportInfo {
as?: string
name?: string
from: string
}
export type SideEffectsInfo = (ImportInfo | string)[] | ImportInfo | string | undefined
export interface ComponentInfo extends ImportInfo {
sideEffects?: SideEffectsInfo
}
export type ComponentResolveResult = Awaitable<string | ComponentInfo | null | undefined | void>
export type ComponentResolverFunction = (name: string) => ComponentResolveResult
export interface ComponentResolverObject {
type: 'component' | 'directive'
resolve: ComponentResolverFunction
}
export type ComponentResolver = ComponentResolverFunction | ComponentResolverObject
interface IMatcher {
pattern: RegExp
styleDir: string
}
const matchComponents: IMatcher[] = [
{
pattern: /^Avatar/,
styleDir: 'avatar'
},
{
pattern: /^AutoComplete/,
styleDir: 'auto-complete'
},
{
pattern: /^Anchor/,
styleDir: 'anchor'
},
{
pattern: /^Badge/,
styleDir: 'badge'
},
{
pattern: /^Breadcrumb/,
styleDir: 'breadcrumb'
},
{
pattern: /^Button/,
styleDir: 'button'
},
{
pattern: /^Checkbox/,
styleDir: 'checkbox'
},
{
pattern: /^Card/,
styleDir: 'card'
},
{
pattern: /^Collapse/,
styleDir: 'collapse'
},
{
pattern: /^Descriptions/,
styleDir: 'descriptions'
},
{
pattern: /^RangePicker|^WeekPicker|^MonthPicker/,
styleDir: 'date-picker'
},
{
pattern: /^Dropdown/,
styleDir: 'dropdown'
},
{
pattern: /^Form/,
styleDir: 'form'
},
{
pattern: /^InputNumber/,
styleDir: 'input-number'
},
{
pattern: /^Input|^Textarea/,
styleDir: 'input'
},
{
pattern: /^Statistic/,
styleDir: 'statistic'
},
{
pattern: /^CheckableTag/,
styleDir: 'tag'
},
{
pattern: /^TimeRangePicker/,
styleDir: 'time-picker'
},
{
pattern: /^Layout/,
styleDir: 'layout'
},
{
pattern: /^Menu|^SubMenu/,
styleDir: 'menu'
},
{
pattern: /^Table/,
styleDir: 'table'
},
{
pattern: /^TimePicker|^TimeRangePicker/,
styleDir: 'time-picker'
},
{
pattern: /^Radio/,
styleDir: 'radio'
},
{
pattern: /^Image/,
styleDir: 'image'
},
{
pattern: /^List/,
styleDir: 'list'
},
{
pattern: /^Tab/,
styleDir: 'tabs'
},
{
pattern: /^Mentions/,
styleDir: 'mentions'
},
{
pattern: /^Step/,
styleDir: 'steps'
},
{
pattern: /^Skeleton/,
styleDir: 'skeleton'
},
{
pattern: /^Select/,
styleDir: 'select'
},
{
pattern: /^TreeSelect/,
styleDir: 'tree-select'
},
{
pattern: /^Tree|^DirectoryTree/,
styleDir: 'tree'
},
{
pattern: /^Typography/,
styleDir: 'typography'
},
{
pattern: /^Timeline/,
styleDir: 'timeline'
},
{
pattern: /^Upload/,
styleDir: 'upload'
}
]
export interface AntDesignResolverOptions {
/**
* exclude components that do not require automatic import
*
* @default []
*/
exclude?: string[]
/**
* import style along with components
*
* @default 'css'
*/
importStyle?: boolean | 'css' | 'less'
/**
* resolve `antd' icons
*
* requires package `@ant-design/icons-vue`
*
* @default false
*/
resolveIcons?: boolean
/**
* @deprecated use `importStyle: 'css'` instead
*/
importCss?: boolean
/**
* @deprecated use `importStyle: 'less'` instead
*/
importLess?: boolean
/**
* use commonjs build default false
*/
cjs?: boolean
/**
* rename package
*
* @default 'antd'
*/
packageName?: string
}
const getStyleDir = (compName: string): string => {
for (const matchComponent of matchComponents) {
if (compName.match(matchComponent.pattern)) {
return matchComponent.styleDir
}
}
return kebabCase(compName)
}
const getSideEffects = (compName: string, options: AntDesignResolverOptions): SideEffectsInfo => {
const { importStyle = true } = options
if (!importStyle) {
return
}
const lib = options.cjs ? 'lib' : 'es'
const packageName = options?.packageName || 'antd'
const styleDir = getStyleDir(compName)
return `${packageName}/${lib}/${styleDir}/style`
}
const primitiveNames = [
'Affix',
'Anchor',
'AnchorLink',
'AutoComplete',
'AutoCompleteOptGroup',
'AutoCompleteOption',
'Alert',
'Avatar',
'AvatarGroup',
'BackTop',
'Badge',
'BadgeRibbon',
'Breadcrumb',
'BreadcrumbItem',
'BreadcrumbSeparator',
'Button',
'ButtonGroup',
'Calendar',
'Card',
'CardGrid',
'CardMeta',
'Collapse',
'CollapsePanel',
'Carousel',
'Cascader',
'Checkbox',
'CheckboxGroup',
'Col',
'Comment',
'ConfigProvider',
'DatePicker',
'MonthPicker',
'WeekPicker',
'RangePicker',
'QuarterPicker',
'Descriptions',
'DescriptionsItem',
'Divider',
'Dropdown',
'DropdownButton',
'Drawer',
'Empty',
'Form',
'FormItem',
'FormItemRest',
'Grid',
'Input',
'InputGroup',
'InputPassword',
'InputSearch',
'Textarea',
'Image',
'ImagePreviewGroup',
'InputNumber',
'Layout',
'LayoutHeader',
'LayoutSider',
'LayoutFooter',
'LayoutContent',
'List',
'ListItem',
'ListItemMeta',
'Menu',
'MenuDivider',
'MenuItem',
'MenuItemGroup',
'SubMenu',
'Mentions',
'MentionsOption',
'Modal',
'Statistic',
'StatisticCountdown',
'PageHeader',
'Pagination',
'Popconfirm',
'Popover',
'Progress',
'Radio',
'RadioButton',
'RadioGroup',
'Rate',
'Result',
'Row',
'Select',
'SelectOptGroup',
'SelectOption',
'Skeleton',
'SkeletonButton',
'SkeletonAvatar',
'SkeletonInput',
'SkeletonImage',
'Slider',
'Space',
'Spin',
'Steps',
'Step',
'Switch',
'Table',
'TableColumn',
'TableColumnGroup',
'TableSummary',
'TableSummaryRow',
'TableSummaryCell',
'Transfer',
'Tree',
'TreeNode',
'DirectoryTree',
'TreeSelect',
'TreeSelectNode',
'Tabs',
'TabPane',
'Tag',
'CheckableTag',
'TimePicker',
'TimeRangePicker',
'Timeline',
'TimelineItem',
'Tooltip',
'Typography',
'TypographyLink',
'TypographyParagraph',
'TypographyText',
'TypographyTitle',
'Upload',
'UploadDragger',
'LocaleProvider'
]
const prefix = 'Antd'
let antdNames: Set<string>
const genAntdNames = (primitiveNames: string[]): void => {
antdNames = new Set(primitiveNames.map((name) => `${prefix}${name}`))
}
genAntdNames(primitiveNames)
const isAntd = (compName: string): boolean => {
return antdNames.has(compName)
}
export function AntDesignResolver(options: AntDesignResolverOptions = {}): ComponentResolver {
return {
type: 'component',
resolve: (name: string) => {
if (options.resolveIcons && name.match(/(Outlined|Filled|TwoTone)$/)) {
return {
name,
from: '@ant-design/icons'
}
}
if (isAntd(name) && !options?.exclude?.includes(name)) {
const importName = name.slice(prefix.length)
const { cjs = false, packageName = 'antd' } = options
const path = `${packageName}/${cjs ? 'lib' : 'es'}`
return {
name: importName,
from: path,
sideEffects: getSideEffects(importName, options)
}
}
}
}
}

View File

@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import { RouterProvider } from 'react-router'
import router from '@/router' import router from '@/router'
const App: React.FC = () => { const App: React.FC = () => {

View File

@@ -1,6 +1,3 @@
import { useMatches, useOutlet } from 'react-router'
import { useMemo } from 'react'
import { Navigate } from 'react-router-dom'
import { getLoginStatus } from '@/utils/auth.ts' import { getLoginStatus } from '@/utils/auth.ts'
const AuthRoute = () => { const AuthRoute = () => {

View File

@@ -3,13 +3,12 @@ import ReactDOM from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import '@/assets/css/base.css' import '@/assets/css/base.css'
import '@/assets/css/common.css' import '@/assets/css/common.css'
import { ConfigProvider } from 'antd'
import zh_CN from 'antd/locale/zh_CN' import zh_CN from 'antd/locale/zh_CN'
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<ConfigProvider locale={zh_CN}> <AntdConfigProvider locale={zh_CN}>
<App /> <App />
</ConfigProvider> </AntdConfigProvider>
</React.StrictMode> </React.StrictMode>
) )

View File

@@ -1,10 +1,7 @@
import React, { useState } from 'react' import React from 'react'
import { Button, Form, Input, message } from 'antd'
import { LockOutlined, UserOutlined } from '@ant-design/icons'
import { login } from '@/utils/auth.ts' import { login } from '@/utils/auth.ts'
import { LOGIN_SUCCESS, LOGIN_USERNAME_PASSWORD_ERROR } from '@/constants/Common.constants.ts' import { LOGIN_SUCCESS, LOGIN_USERNAME_PASSWORD_ERROR } from '@/constants/Common.constants.ts'
import { setToken } from '@/utils/common.ts' import { setToken } from '@/utils/common.ts'
import { useNavigate } from 'react-router'
import '@/assets/css/login.css' import '@/assets/css/login.css'
const Login: React.FC = () => { const Login: React.FC = () => {
@@ -59,36 +56,36 @@ const Login: React.FC = () => {
<div className={'login-from-text'}> <div className={'login-from-text'}>
<span>&ensp;</span> <span>&ensp;</span>
</div> </div>
<Form <AntdForm
name="login-form" name="login-form"
autoComplete="on" autoComplete="on"
onFinish={onFinish} onFinish={onFinish}
className={'login-from'} className={'login-from'}
> >
<Form.Item <AntdForm.Item
className={'login-from-item'} className={'login-from-item'}
name={'username'} name={'username'}
rules={[{ required: true, message: '用户名为空' }]} rules={[{ required: true, message: '用户名为空' }]}
> >
<Input <AntdInput
prefix={<UserOutlined />} prefix={<UserOutlined />}
placeholder={'用户名'} placeholder={'用户名'}
disabled={isLoggingIn} disabled={isLoggingIn}
/> />
</Form.Item> </AntdForm.Item>
<Form.Item <AntdForm.Item
className={'login-from-item'} className={'login-from-item'}
name={'password'} name={'password'}
rules={[{ required: true, message: '密码为空' }]} rules={[{ required: true, message: '密码为空' }]}
> >
<Input.Password <AntdInput.Password
prefix={<LockOutlined />} prefix={<LockOutlined />}
placeholder={'密码'} placeholder={'密码'}
disabled={isLoggingIn} disabled={isLoggingIn}
/> />
</Form.Item> </AntdForm.Item>
<Form.Item className={'login-from-item'}> <AntdForm.Item className={'login-from-item'}>
<Button <AntdButton
style={{ width: '100%' }} style={{ width: '100%' }}
type={'primary'} type={'primary'}
htmlType={'submit'} htmlType={'submit'}
@@ -96,9 +93,9 @@ const Login: React.FC = () => {
loading={isLoggingIn} loading={isLoggingIn}
> >
&ensp;&ensp;&ensp;&ensp; &ensp;&ensp;&ensp;&ensp;
</Button> </AntdButton>
</Form.Item> </AntdForm.Item>
</Form> </AntdForm>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,7 @@
import React from 'react' import React from 'react'
import { Button, Checkbox, DatePicker, Input, Select, Space, Table } from 'antd'
import { removeToken } from '@/utils/common.ts' import { removeToken } from '@/utils/common.ts'
import { logout } from '@/utils/auth.ts' import { logout } from '@/utils/auth.ts'
import '@/assets/css/manager.css' import '@/assets/css/manager.css'
import { DeleteOutlined, SearchOutlined } from '@ant-design/icons'
import { ColumnsType } from 'antd/es/table' import { ColumnsType } from 'antd/es/table'
type OrderDataType = { type OrderDataType = {
@@ -73,11 +71,11 @@ const columns: ColumnsType<OrderDataType> = [
title: '操作', title: '操作',
key: 'action', key: 'action',
render: () => ( render: () => (
<Space size={'middle'}> <AntdSpace size={'middle'}>
<a style={{ color: 'skyblue' }}></a> <a style={{ color: 'skyblue' }}></a>
<a style={{ color: 'skyblue' }}></a> <a style={{ color: 'skyblue' }}></a>
<a style={{ color: 'skyblue' }}></a> <a style={{ color: 'skyblue' }}></a>
</Space> </AntdSpace>
) )
} }
] ]
@@ -140,15 +138,15 @@ const Manager: React.FC = () => {
<> <>
<div className={'body'}> <div className={'body'}>
<div className={'top-bar'}> <div className={'top-bar'}>
<Button onClick={handleOnClick} ghost> <AntdButton onClick={handleOnClick} ghost>
退 退
</Button> </AntdButton>
</div> </div>
<div className={'search-bar'}> <div className={'search-bar'}>
<div className={'search-row'}> <div className={'search-row'}>
<label> <label>
<Select <AntdSelect
defaultValue={1} defaultValue={1}
options={[{ value: 1, label: '创建日期' }]} options={[{ value: 1, label: '创建日期' }]}
showSearch showSearch
@@ -161,15 +159,15 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<DatePicker /> <AntdDatePicker />
</label> </label>
<label> <label>
<DatePicker /> <AntdDatePicker />
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -183,7 +181,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -197,7 +195,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -213,23 +211,23 @@ const Manager: React.FC = () => {
<div className={'search-row'}> <div className={'search-row'}>
<label> <label>
<Input placeholder={'平台单号'} /> <AntdInput placeholder={'平台单号'} />
</label> </label>
<label> <label>
<Input placeholder={'系统单号'} /> <AntdInput placeholder={'系统单号'} />
</label> </label>
<label> <label>
B2B单号 B2B单号
<Input placeholder={'B2B单号'} /> <AntdInput placeholder={'B2B单号'} />
</label> </label>
<label> <label>
<Input placeholder={'供应商单号'} /> <AntdInput placeholder={'供应商单号'} />
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -243,7 +241,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -259,19 +257,19 @@ const Manager: React.FC = () => {
<div className={'search-row'}> <div className={'search-row'}>
<label> <label>
<Input placeholder={'酒店名称'} /> <AntdInput placeholder={'酒店名称'} />
</label> </label>
<label> <label>
<Input placeholder={'客人信息'} /> <AntdInput placeholder={'客人信息'} />
</label> </label>
<label> <label>
<Input placeholder={'成本总额'} /> <AntdInput placeholder={'成本总额'} />
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -285,7 +283,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -297,12 +295,12 @@ const Manager: React.FC = () => {
} }
/> />
</label> </label>
<Checkbox>稿</Checkbox> <AntdCheckbox>稿</AntdCheckbox>
</div> </div>
<div className={'search-row'}> <div className={'search-row'}>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -316,7 +314,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -330,7 +328,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -344,7 +342,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -358,7 +356,7 @@ const Manager: React.FC = () => {
</label> </label>
<label> <label>
<Select <AntdSelect
defaultValue={0} defaultValue={0}
allowClear allowClear
showSearch showSearch
@@ -371,22 +369,22 @@ const Manager: React.FC = () => {
/> />
</label> </label>
<div className={'operation-buttons'}> <div className={'operation-buttons'}>
<Button type={'primary'} icon={<SearchOutlined />}> <AntdButton type={'primary'} icon={<SearchOutlined />}>
</Button> </AntdButton>
<Button type={'dashed'} icon={<DeleteOutlined />}> <AntdButton type={'dashed'} icon={<DeleteOutlined />}>
</Button> </AntdButton>
</div> </div>
</div> </div>
<div className={'operation-buttons'} style={{ marginRight: '10px' }}> <div className={'operation-buttons'} style={{ marginRight: '10px' }}>
<Button type={'primary'}></Button> <AntdButton type={'primary'}></AntdButton>
<Button type={'primary'}></Button> <AntdButton type={'primary'}></AntdButton>
<Button type={'primary'}></Button> <AntdButton type={'primary'}></AntdButton>
<Button type={'primary'}></Button> <AntdButton type={'primary'}></AntdButton>
</div> </div>
</div> </div>
<Table columns={columns} dataSource={data} style={{ marginTop: '20px' }} /> <AntdTable columns={columns} dataSource={data} style={{ marginTop: '20px' }} />
</div> </div>
</> </>
) )

View File

@@ -1,4 +1,3 @@
import { createBrowserRouter, Navigate, RouteObject } from 'react-router-dom'
import Login from '@/pages/Login.tsx' import Login from '@/pages/Login.tsx'
import Manager from '@/pages/Manager.tsx' import Manager from '@/pages/Manager.tsx'
import AuthRoute from '@/AuthRoute.tsx' import AuthRoute from '@/AuthRoute.tsx'
@@ -10,23 +9,18 @@ const routes: RouteObject[] = [
children: [ children: [
{ {
path: '/login', path: '/login',
element: <Login />, id: 'login',
children: [ element: <Login />
{
id: 'login',
path: '1',
element: <Manager />
}
]
}, },
{ {
path: '', path: '',
id: 'manager',
element: <Manager />, element: <Manager />,
children: [ children: [
{ {
id: 'manager', id: 'manager-sub',
path: '1', path: 'sub',
element: <Login /> element: <Manager />
} }
], ],
handle: { handle: {

View File

@@ -2,7 +2,6 @@
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
@@ -26,6 +25,6 @@
"types": ["unplugin-icons/types/react"] "types": ["unplugin-icons/types/react"]
}, },
"include": ["src"], "include": ["src", "auto-imports.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }

View File

@@ -7,5 +7,5 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts", "build/**/*"]
} }

View File

@@ -6,6 +6,7 @@ import Icons from 'unplugin-icons/vite'
import { FileSystemIconLoader } from 'unplugin-icons/loaders' import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver' import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite' import AutoImport from 'unplugin-auto-import/vite'
import { AntDesignResolver } from './build/resolvers/antd'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@@ -13,13 +14,54 @@ export default defineConfig({
react(), react(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call
AutoImport({ AutoImport({
// targets to transform
include: [
/\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/\.md$/ // .md
],
// global imports to register
imports: [
'react',
'react-router',
'react-router-dom',
{
'react-router': ['useMatches', 'RouterProvider'],
'react-router-dom': ['createBrowserRouter'],
antd: ['message']
},
{
from: 'react-router',
imports: ['RouteObject'],
type: true
}
],
// Filepath to generate corresponding .d.ts file.
// Defaults to './auto-imports.d.ts' when `typescript` is installed locally.
// Set `false` to disable.
dts: './auto-imports.d.ts',
// Custom resolvers, compatible with `unplugin-vue-components`
// see https://github.com/antfu/unplugin-auto-import/pull/23/
resolvers: [ resolvers: [
IconsResolver({ IconsResolver({
prefix: 'icon', prefix: 'icon',
extension: 'jsx', extension: 'jsx',
customCollections: ['framework'] customCollections: ['framework']
}),
AntDesignResolver({
resolveIcons: true
}) })
] ],
// Generate corresponding .eslintrc-auto-import.json file.
// eslint globals Docs - https://eslint.org/docs/user-guide/configuring/language-options#specifying-globals
eslintrc: {
enabled: true, // Default `false`
filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`
globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
}
}) as PluginOption, }) as PluginOption,
Icons({ Icons({
compiler: 'jsx', compiler: 'jsx',