前端图标库
Oak 前端图标库围绕 OakIcon 组件工作。它解决的不是“怎么在某个 React 页面里临时引入一个图标”,而是让页面配置、菜单配置、web 运行时和小程序编译都能用同一套字符串图标名。
最重要的规则只有一条:
- 页面、菜单、namespace 配置里只写字符串;
- 真正的图标库实现写在
oak.config.ts、web 初始化 options 或 namespace config 里; - 渲染层统一把字符串交给
OakIcon。
这样做之后,oak-cli 才能在编译时生成路由、菜单、web 按需加载和小程序图标样式。
基本用法
业务代码里统一使用 OakIcon:
import OakIcon from '@oak-frontend-base/components/icon';
<OakIcon name="oak:setup_fill" size={18} />
<OakIcon name="trip:hotel" size={18} />
<OakIcon name="antd:ShopOutlined" size={18} />
在页面、菜单、namespace 配置里也只写字符串:
icon: 'antd:ShopOutlined'
不要在 CreatePageConfig(...) 或 CreateNamespaceConfig(...) 中写 ReactNode,例如不要写:
icon: <ShopOutlined />
这些配置需要保持可序列化。否则路由生成、菜单生成和按需图标加载都会失去静态分析基础。
图标名怎么写
Oak 当前支持三类常见写法。
1. Oak 内置图标
旧项目里常见的写法仍然可用:
icon: 'setup_fill'
这等价于:
icon: 'oak:setup_fill'
新代码更推荐显式写 oak::
icon: 'oak:setup_fill'
icon: 'oak:tasklist'
oak:* 来自 oak-frontend-base 内置 iconfont,web 和小程序都支持。
2. 自定义 font 图标
业务自己的字体图标使用 <library>:<name>:
icon: 'trip:hotel'
icon: 'trip:order'
其中 trip 是图标库名,hotel / order 是业务图标名。
3. Web React 图标
web 端可以使用 React 图标库,例如 Ant Design Icons:
icon: 'antd:ShopOutlined'
icon: 'antd:SettingOutlined'
type: 'react' 只支持 web。小程序不能直接渲染 React 图标组件。
在 oak.config.ts 中配置
跨端通用配置写在项目根目录 oak.config.ts 的 frontend.iconLibraries 下。
使用已有 iconfont 样式
如果项目已经有 iconfont 的 CSS / Less 文件,可以这样配置:
import { CreateCompilerConfig } from '@xuchangzju/oak-cli/lib/createConfig';
export default CreateCompilerConfig({
frontend: {
iconLibraries: [
{
name: 'trip',
type: 'font',
fontFamily: 'trip-iconfont',
fontFamilyClass: 'trip-iconfont',
classPrefix: 'trip-icon-',
style: '@project/assets/icons/trip/iconfont.less',
subset: 'auto',
},
],
},
});
这些字段的含义是:
name:图标库名,对应图标字符串里的trip:;type: 'font':字体图标库,web 和小程序都能用;fontFamily:匹配@font-face中的字体名;fontFamilyClass:运行时加到非oak图标上的字体 class;classPrefix:glyph class 前缀;style:已有 iconfont 样式文件;subset:小程序端是否裁剪未使用的 glyph。
用 fontUrl + icons 生成样式
如果项目不想维护完整 iconfont 样式,也可以只给字体文件和 glyph 映射:
import { CreateCompilerConfig } from '@xuchangzju/oak-cli/lib/createConfig';
export default CreateCompilerConfig({
frontend: {
iconLibraries: [
{
name: 'trip',
type: 'font',
fontFamily: 'trip-iconfont',
fontFamilyClass: 'trip-iconfont',
classPrefix: 'trip-icon-',
fontUrl: '@project/assets/icons/trip/iconfont.woff2',
fontFormat: 'woff2',
icons: {
hotel: '\\e600',
order: '\\e601',
},
subset: 'auto',
},
],
},
});
fontUrl 只说明字体文件在哪里,icons 才说明业务图标名和 glyph content 的对应关系。只配置字体文件,Oak 不能自动猜出 hotel、order 这些业务名。
运行时 trip:hotel 会生成类似这样的 class:
oak-icon trip-iconfont trip-icon-hotel oak-icon__primary
按平台配置
如果 web 和小程序使用不同图标库,可以放到 frontend.targets 下:
import { CreateCompilerConfig } from '@xuchangzju/oak-cli/lib/createConfig';
export default CreateCompilerConfig({
frontend: {
targets: {
web: {
iconLibraries: [
{
name: 'antd',
type: 'react',
packageName: '@ant-design/icons',
platforms: ['web'],
},
],
},
wechatMp: {
iconLibraries: [
{
name: 'trip',
type: 'font',
fontFamilyClass: 'trip-iconfont',
classPrefix: 'trip-icon-',
style: '@project/assets/icons/trip/iconfont.less',
subset: 'auto',
},
],
},
},
},
});
targets.web 只影响 web 编译,targets.wechatMp 只影响小程序编译。
在 namespace 中配置
某些后台 namespace 自己需要一套图标库时,可以写在:
web/src/app/namespaces/<namespace>/index.config.ts
示例:
import { CreateNamespaceConfig } from '@oak-frontend-base/config';
export default CreateNamespaceConfig({
iconLibraries: [
{
name: 'antd',
type: 'react',
packageName: '@ant-design/icons',
platforms: ['web'],
},
],
menu: {
groups: [
{
name: 'HotelOps',
icon: 'antd:ShopOutlined',
order: 1,
},
],
},
});
namespace config 里的 iconLibraries 会参与 web 初始化。菜单仍然只写字符串。
在页面菜单中使用
页面菜单项通常写在页面旁边的 index.config.ts:
import { CreatePageConfig } from '@oak-frontend-base/config';
export default CreatePageConfig({
menu: {
name: 'hotelArchive',
icon: 'antd:ShopOutlined',
group: 'HotelOps',
order: 1,
},
});
菜单渲染组件只需要把这个字符串交给 OakIcon:
<OakIcon name={icon || ''} size={18} />
不要在菜单数据里提前构造 React 组件。菜单数据应该保持平台无关和可序列化。
Web React 图标按需加载
React 图标库有两种接法。
1. 手工注册已导入组件
如果只用几个图标,可以在 web 初始化时显式导入:
import {
SettingOutlined,
ShopOutlined,
} from '@ant-design/icons';
initialize(features, appName, routers, {
namespaceConfigs,
iconLibraries: [
{
name: 'antd',
type: 'react',
platforms: ['web'],
icons: {
SettingOutlined,
ShopOutlined,
},
},
],
});
这种方式最直接,但要避免:
import * as Icons from '@ant-design/icons';
全量导入图标库会明显增加入口包体积。
2. 让 oak-cli 生成按需 loader
更推荐在 oak.config.ts 里声明 packageName:
{
name: 'antd',
type: 'react',
packageName: '@ant-design/icons',
platforms: ['web'],
}
oak-cli 会扫描静态图标字符串,例如:
icon: 'antd:ShopOutlined'
然后生成类似下面的动态 import:
import('@ant-design/icons/ShopOutlined')
默认 import 规则是:
{packageName}/{iconName}
如果图标包路径不同,可以配置 importPath:
{
name: 'custom',
type: 'react',
packageName: '@scope/icons',
importPath: '{packageName}/icons/{iconName}',
platforms: ['web'],
}
这个机制是编译期静态扫描,不是运行时任意字符串 import。动态拼接图标名不保证有对应 loader:
// 不推荐:编译器无法稳定收集所有图标
icon: `antd:${name}`
小程序 font 图标裁剪
小程序只能处理 oak:* 和 type: 'font' 图标库,不处理 type: 'react'。
小程序编译时,oak-cli 会把 font 图标样式合并进:
components/icon/index.wxss
同时它会扫描当前小程序入口图里的页面和组件,收集静态使用的图标名,例如:
<oak-icon name="trip:hotel" />
或者:
<OakIcon name="trip:hotel" />
subset 控制裁剪策略:
subset: 'auto':推荐。静态可判断时只保留用到的 glyph;发现动态图标名时回退整库并打印 warning;subset: true:强制按静态扫描结果裁剪;subset: false:保留整库 CSS 和字体。
如果图标名有动态拼接,优先用 subset: 'auto' 或 subset: false,避免小程序运行时才用到的 glyph 被裁掉。
常见排错
图标不显示时,先按下面顺序检查。
通用检查
- 图标名是否是静态字符串;
- 图标名前缀是否和图标库
name一致; oak.config.ts、namespace config 修改后是否重启了 dev server;- 页面、菜单、namespace 配置里是否误写了 ReactNode;
fontUrl是否同时配了icons映射。
Web 图标不显示
type: 'react'的依赖包是否已安装,例如@ant-design/icons;packageName对应的单图标入口是否真实存在;- 单图标入口不是
{packageName}/{iconName}时,是否配置了importPath; - 手工注册时是否只导入了用到的图标组件;
type: 'font'时,web 端是否能拿到对应样式或编译生成的styleText。
小程序图标不显示
- 小程序是否误用了
antd:*这类 React 图标; style指向的 iconfont 样式文件是否能被oak-cli解析;fontUrl指向的字体文件是否存在;- 动态图标名是否被
subset: true裁掉; - 构建日志中是否出现
[oak-vite-mp-icon]warning。
当前限制
type: 'react'只支持 web;type: 'image'目前只是类型预留,还不是完整的跨端图标资源方案;type: 'react' + packageName依赖编译期静态扫描,动态拼接图标名不会自动生成任意 import;- 小程序图标裁剪只扫描当前小程序入口图相关文件,不会无条件扫描项目里所有源码。