@manohub/icon 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -5
- package/bin/icon-from-svg.mjs +145 -0
- package/dist/business-glyphs.d.ts +35 -0
- package/dist/business-glyphs.js +50 -0
- package/dist/glyphs.d.ts +14 -23
- package/dist/glyphs.js +12 -0
- package/dist/icon.d.ts +32 -16
- package/dist/icon.js +63 -21
- package/dist/svg.d.ts +82 -0
- package/dist/svg.js +308 -0
- package/package.json +9 -1
package/dist/svg.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG 归一:把「完整 `.svg`」转成图标内容表要的**片段串**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:表里存的是**片段**(不含 `<svg>` 壳、不含硬编码颜色),而项目手上多半是
|
|
5
|
+
* 设计工具 / 图标站导出的**完整文件**。手工剥壳有 7 件事要做,其中三件几乎必错
|
|
6
|
+
* (算多层 `transform` 的净矩阵、保住 `fill-rule`、查重),所以做成工具。
|
|
7
|
+
*
|
|
8
|
+
* 用法只有一条:**构建期** —— `icon-from-svg` 读一个目录,产出静态的 `.ts` 表(片段进 git,可 review、可 diff),
|
|
9
|
+
* 片段再交给 `<Icon>` 的默认插槽渲染。本包**不在运行期做归一** —— 那是每个消费方的包里都要背一遍的解析器。
|
|
10
|
+
*
|
|
11
|
+
* 本文件是**纯字符串处理**:不引 `vue`、不引 `node:*`、零依赖 —— 所以浏览器与 Node 都能用
|
|
12
|
+
* (读文件那半边在 `bin/icon-from-svg.mjs`)。
|
|
13
|
+
*
|
|
14
|
+
* 口径与「设计净稿规范」一致:只接收**单色**图形;渐变 / 滤镜 / 遮罩 / 多色一律**拒绝**
|
|
15
|
+
* (那类属于产品专属图形,应留在消费方 assets,不进字形表)。
|
|
16
|
+
*/
|
|
17
|
+
/** 可绘制的元素 */
|
|
18
|
+
const DRAWABLE = new Set(['path', 'rect', 'circle', 'ellipse', 'polygon', 'polyline', 'line']);
|
|
19
|
+
/** 片段里要保留的几何属性(其余属性一律丢弃:`id` / `class` / `xml-*` / `data-*` / 导出残留) */
|
|
20
|
+
const GEOMETRY = [
|
|
21
|
+
'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'cx', 'cy', 'r', 'points', 'x1', 'y1', 'x2', 'y2'
|
|
22
|
+
];
|
|
23
|
+
/** 一出现就**拒绝**的元素:单色片段表达不了它们 */
|
|
24
|
+
const REJECT_TAGS = {
|
|
25
|
+
lineargradient: '线性渐变',
|
|
26
|
+
radialgradient: '径向渐变',
|
|
27
|
+
filter: '滤镜',
|
|
28
|
+
mask: '蒙版',
|
|
29
|
+
clippath: '裁剪路径',
|
|
30
|
+
pattern: '填充图案',
|
|
31
|
+
defs: '定义块(渐变 / 蒙版 / 符号等)',
|
|
32
|
+
use: '引用(use)',
|
|
33
|
+
symbol: '符号',
|
|
34
|
+
marker: '标记',
|
|
35
|
+
image: '位图',
|
|
36
|
+
style: '内联样式表(片段里无法作用域隔离)'
|
|
37
|
+
};
|
|
38
|
+
const TAG = /<(\/?)([A-Za-z][\w:.-]*)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>/g;
|
|
39
|
+
/* ============================ 小工具 ============================ */
|
|
40
|
+
function parseAttrs(attrStr) {
|
|
41
|
+
const out = {};
|
|
42
|
+
const re = /([\w:.-]+)\s*=\s*"([^"]*)"/g;
|
|
43
|
+
let m;
|
|
44
|
+
while ((m = re.exec(attrStr)))
|
|
45
|
+
out[m[1].toLowerCase()] = m[2];
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
const IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
49
|
+
/** 先施加 `n`、再施加 `m` */
|
|
50
|
+
function mul(m, n) {
|
|
51
|
+
return [
|
|
52
|
+
m[0] * n[0] + m[2] * n[1],
|
|
53
|
+
m[1] * n[0] + m[3] * n[1],
|
|
54
|
+
m[0] * n[2] + m[2] * n[3],
|
|
55
|
+
m[1] * n[2] + m[3] * n[3],
|
|
56
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
57
|
+
m[1] * n[4] + m[3] * n[5] + m[5]
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function parseTransform(str, warnings) {
|
|
61
|
+
let m = IDENTITY;
|
|
62
|
+
const re = /(translate|scale|matrix|rotate|skewX|skewY)\s*\(([^)]*)\)/gi;
|
|
63
|
+
let t;
|
|
64
|
+
while ((t = re.exec(str))) {
|
|
65
|
+
const kind = t[1].toLowerCase();
|
|
66
|
+
const nums = t[2].split(/[\s,]+/).filter(Boolean).map(Number);
|
|
67
|
+
let n = null;
|
|
68
|
+
if (kind === 'translate')
|
|
69
|
+
n = [1, 0, 0, 1, nums[0] ?? 0, nums[1] ?? 0];
|
|
70
|
+
else if (kind === 'scale')
|
|
71
|
+
n = [nums[0] ?? 1, 0, 0, nums[1] ?? nums[0] ?? 1, 0, 0];
|
|
72
|
+
else if (kind === 'matrix')
|
|
73
|
+
n = nums;
|
|
74
|
+
else
|
|
75
|
+
warnings.push(`含 ${kind}() —— 该变换无法在不改写路径的前提下归一,已按原样保留`);
|
|
76
|
+
if (n)
|
|
77
|
+
m = mul(m, n);
|
|
78
|
+
}
|
|
79
|
+
return m;
|
|
80
|
+
}
|
|
81
|
+
/** 数字序列化:去掉浮点噪声,且不留 `1.50` 这种尾巴 */
|
|
82
|
+
function num(n) {
|
|
83
|
+
return String(Math.round(n * 1e6) / 1e6);
|
|
84
|
+
}
|
|
85
|
+
const isIdentity = (m) => m.every((v, i) => v === IDENTITY[i]);
|
|
86
|
+
const sameMatrix = (a, b) => a.every((v, i) => v === b[i]);
|
|
87
|
+
/** 矩阵 → `transform` 串(无旋转 / 斜切时写成更易读的 `translate() scale()`) */
|
|
88
|
+
function matrixToTransform(m) {
|
|
89
|
+
const [a, b, c, d, e, f] = m;
|
|
90
|
+
if (b === 0 && c === 0) {
|
|
91
|
+
const parts = [];
|
|
92
|
+
if (e !== 0 || f !== 0)
|
|
93
|
+
parts.push(`translate(${num(e)}, ${num(f)})`);
|
|
94
|
+
if (a !== 1)
|
|
95
|
+
parts.push(`scale(${num(a)})`);
|
|
96
|
+
return parts.join(' ');
|
|
97
|
+
}
|
|
98
|
+
return `matrix(${m.map(num).join(' ')})`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 完整 SVG 串 → 片段串。**入参不合法就抛错**(构建期应当直接失败,别写出半成品表)。
|
|
102
|
+
*
|
|
103
|
+
* 会做的事:剥壳与导出残留、合并多层 `transform`、单色归一为 `currentColor`、
|
|
104
|
+
* 视口换算(包一层 `transform`,**不改写路径坐标**)、保住 `fill-rule`。
|
|
105
|
+
* 拒绝的事:渐变 / 滤镜 / 蒙版 / 裁剪 / 内联样式表 / 多色 —— 报错信息里会说清是哪一个。
|
|
106
|
+
*/
|
|
107
|
+
export function svgToFragment(svgText, options = {}) {
|
|
108
|
+
const target = options.size ?? 24;
|
|
109
|
+
const warnings = [];
|
|
110
|
+
const text = svgText.replace(/^\uFEFF/, '');
|
|
111
|
+
const openTag = text.match(/<svg\b[^>]*>/i);
|
|
112
|
+
if (!openTag)
|
|
113
|
+
throw new Error('不是一个 SVG 文件:找不到 <svg> 开标签');
|
|
114
|
+
const svgAttrs = parseAttrs(openTag[0]);
|
|
115
|
+
const viewBox = svgAttrs.viewbox ?? '';
|
|
116
|
+
const vbNums = viewBox.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
117
|
+
const width = vbNums.length === 4 ? vbNums[2] : Number(svgAttrs.width) || 0;
|
|
118
|
+
const height = vbNums.length === 4 ? vbNums[3] : Number(svgAttrs.height) || 0;
|
|
119
|
+
if (!width || !height) {
|
|
120
|
+
throw new Error('画板尺寸不明:`viewBox` 与 `width` / `height` 都没有可用的数值');
|
|
121
|
+
}
|
|
122
|
+
const side = Math.max(width, height);
|
|
123
|
+
if (width !== height) {
|
|
124
|
+
warnings.push(`画板非正方形(${num(width)}×${num(height)}),按长边 ${num(side)} 换算,短边一侧会有留白`);
|
|
125
|
+
}
|
|
126
|
+
const scale = target / side;
|
|
127
|
+
// 只取 <svg> 标签之间的内容(外层壳与 <title> 一律不要)
|
|
128
|
+
const inner = text.slice(text.indexOf('>', text.indexOf(openTag[0])) + 1).replace(/<\/svg\s*>[\s\S]*$/i, '');
|
|
129
|
+
const items = [];
|
|
130
|
+
const colors = new Set();
|
|
131
|
+
const strokeWidths = new Set();
|
|
132
|
+
let dropped = 0;
|
|
133
|
+
let strokeBased = false;
|
|
134
|
+
const root = { matrix: IDENTITY, opacity: 1 };
|
|
135
|
+
const stack = [root];
|
|
136
|
+
let m;
|
|
137
|
+
TAG.lastIndex = 0;
|
|
138
|
+
while ((m = TAG.exec(inner))) {
|
|
139
|
+
const [, close, rawName, attrStr, selfClose] = m;
|
|
140
|
+
const name = rawName.toLowerCase();
|
|
141
|
+
if (name === 'svg')
|
|
142
|
+
throw new Error('内容里又出现一个 <svg>(嵌套 SVG 无法归一)');
|
|
143
|
+
const reject = REJECT_TAGS[name];
|
|
144
|
+
if (reject)
|
|
145
|
+
throw new Error(`含 ${reject} <${rawName}>:单色片段表达不了,属产品专属图形,请放应用侧 assets`);
|
|
146
|
+
if (close) {
|
|
147
|
+
if (stack.length > 1)
|
|
148
|
+
stack.pop();
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const parent = stack[stack.length - 1];
|
|
152
|
+
const attrs = parseAttrs(attrStr);
|
|
153
|
+
const opacity = (attrs.opacity === undefined ? 1 : Number(attrs.opacity)) * parent.opacity;
|
|
154
|
+
const node = {
|
|
155
|
+
matrix: attrs.transform ? mul(parent.matrix, parseTransform(attrs.transform, warnings)) : parent.matrix,
|
|
156
|
+
fill: attrs.fill ?? parent.fill,
|
|
157
|
+
stroke: attrs.stroke ?? parent.stroke,
|
|
158
|
+
fillRule: attrs['fill-rule'] ?? parent.fillRule,
|
|
159
|
+
strokeWidth: attrs['stroke-width'] ?? parent.strokeWidth,
|
|
160
|
+
opacity
|
|
161
|
+
};
|
|
162
|
+
if (DRAWABLE.has(name)) {
|
|
163
|
+
const isPlaceholder = opacity === 0;
|
|
164
|
+
if (isPlaceholder) {
|
|
165
|
+
// 设计稿常见的「透明占位底」(16×16 空 rect);若它带可见子元素则不是占位,直接拦下
|
|
166
|
+
if (!selfClose)
|
|
167
|
+
dropped++;
|
|
168
|
+
else
|
|
169
|
+
dropped++;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
items.push({ tag: name, attrs, matrix: node.matrix, fill: node.fill, stroke: node.stroke, fillRule: node.fillRule, opacity, strokeWidth: node.strokeWidth });
|
|
173
|
+
if (node.fill && node.fill !== 'none')
|
|
174
|
+
colors.add(node.fill);
|
|
175
|
+
if (node.stroke && node.stroke !== 'none')
|
|
176
|
+
colors.add(node.stroke);
|
|
177
|
+
if (node.strokeWidth)
|
|
178
|
+
strokeWidths.add(node.strokeWidth);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!selfClose)
|
|
182
|
+
stack.push(node);
|
|
183
|
+
}
|
|
184
|
+
if (!items.length)
|
|
185
|
+
throw new Error('没扫到任何图形元素(可能全是透明占位或无内容)');
|
|
186
|
+
const filled = items.filter((it) => (it.fill ?? 'none') !== 'none');
|
|
187
|
+
strokeBased = filled.length === 0 && items.some((it) => (it.stroke ?? 'none') !== 'none');
|
|
188
|
+
// 颜色:只允许「零种(完全继承)」或「一种」;两种以上就是多色图形
|
|
189
|
+
const palette = [...colors].filter((c) => c !== 'currentColor');
|
|
190
|
+
if (palette.length > 1) {
|
|
191
|
+
throw new Error(`含 ${palette.length} 种颜色(${palette.join(' / ')}):多色图形请放应用侧 assets`);
|
|
192
|
+
}
|
|
193
|
+
if (strokeWidths.size > 1) {
|
|
194
|
+
warnings.push(`各元素线重不一致(${[...strokeWidths].join(' / ')}),已按原值保留到片段上`);
|
|
195
|
+
}
|
|
196
|
+
// 填充规则:全部一致才提到包装层,不一致就逐个写
|
|
197
|
+
const rules = [...new Set(items.map((it) => it.fillRule ?? 'nonzero'))];
|
|
198
|
+
if (rules.length > 1)
|
|
199
|
+
warnings.push(`各元素 fill-rule 不一致(${rules.join(' / ')}),已逐个保留`);
|
|
200
|
+
// 矩阵:全部一致就提到包装层(设计稿多导出一层画板偏移,净矩阵常为单位阵)
|
|
201
|
+
const matrixes = items.map((it) => it.matrix);
|
|
202
|
+
const uniform = matrixes.every((mm) => sameMatrix(mm, matrixes[0]));
|
|
203
|
+
const body = items.map((it, i) => {
|
|
204
|
+
const keep = GEOMETRY.filter((k) => it.attrs[k] !== undefined).map((k) => `${k}="${it.attrs[k]}"`);
|
|
205
|
+
if (rules.length > 1)
|
|
206
|
+
keep.push(`fill-rule="${it.fillRule ?? 'nonzero'}"`);
|
|
207
|
+
if (it.opacity !== 1)
|
|
208
|
+
keep.push(`opacity="${num(it.opacity)}"`);
|
|
209
|
+
const geom = `<${it.tag} ${keep.join(' ')}/>`;
|
|
210
|
+
if (uniform)
|
|
211
|
+
return geom;
|
|
212
|
+
// 坐标系不一致时逐个套自己的矩阵(不做展平改写,保持路径数据原样)
|
|
213
|
+
const own = matrixToTransform(it.matrix);
|
|
214
|
+
return own ? `<g transform="${own}">${geom}</g>` : geom;
|
|
215
|
+
}).join('');
|
|
216
|
+
// 包装层:外层视口换算 × (一致时)元素矩阵
|
|
217
|
+
const viewport = [scale, 0, 0, scale, 0, 0];
|
|
218
|
+
const wrap = uniform ? mul(viewport, matrixes[0]) : viewport;
|
|
219
|
+
const transform = isIdentity(wrap) ? '' : ` transform="${matrixToTransform(wrap)}"`;
|
|
220
|
+
const paint = strokeBased
|
|
221
|
+
? ' fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"'
|
|
222
|
+
: ' fill="currentColor" stroke="none"';
|
|
223
|
+
const ruleAttr = rules.length === 1 && rules[0] !== 'nonzero' ? ` fill-rule="${rules[0]}"` : '';
|
|
224
|
+
const fragment = `<g${transform}${paint}${ruleAttr}>${body}</g>`;
|
|
225
|
+
if (strokeBased) {
|
|
226
|
+
const sw = strokeWidths.size === 1 ? [...strokeWidths][0] : undefined;
|
|
227
|
+
warnings.push(`这是**描边型**图形${sw ? `(原线重 ${sw},原画板 ${num(side)} 口径)` : ''}:` +
|
|
228
|
+
`渲染方的 <svg> 上写死 stroke-width="2",` +
|
|
229
|
+
`${sw && Math.abs(Number(sw) * scale - 2) > 0.05 ? `此稿换算后约 ${num(Number(sw) * scale)},如需保真请在片段里显式写 stroke-width` : '如需改线重请在片段里显式写 stroke-width'}`);
|
|
230
|
+
}
|
|
231
|
+
if (dropped)
|
|
232
|
+
warnings.push(`丢弃了 ${dropped} 个透明元素(opacity="0",通常是设计稿的占位底)`);
|
|
233
|
+
return {
|
|
234
|
+
fragment,
|
|
235
|
+
warnings,
|
|
236
|
+
info: {
|
|
237
|
+
side,
|
|
238
|
+
viewBox,
|
|
239
|
+
scale,
|
|
240
|
+
elements: items.length,
|
|
241
|
+
fillRule: rules.length === 1 ? rules[0] : 'mixed',
|
|
242
|
+
strokeBased,
|
|
243
|
+
strokeWidth: strokeBased && strokeWidths.size === 1 ? Number([...strokeWidths][0]) : undefined
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 生成静态内容表的 TS 源码(`icon-from-svg` 的产物)。
|
|
249
|
+
*
|
|
250
|
+
* 生成物是**可 review 的源码**,不是运行期拼的 —— 这是本工具走构建期的全部理由。
|
|
251
|
+
*/
|
|
252
|
+
export function buildTableSource(entries, options = {}) {
|
|
253
|
+
const constName = options.constName ?? 'PROJECT_GLYPHS';
|
|
254
|
+
const rows = entries.map((e) => {
|
|
255
|
+
const notes = e.notes?.length ? e.notes.map((n) => ` // ${n}\n`).join('') : '';
|
|
256
|
+
const source = e.source ? ` // 源文件:${e.source}\n` : '';
|
|
257
|
+
return `${notes}${source} '${e.name}': '${e.fragment}',`;
|
|
258
|
+
}).join('\n');
|
|
259
|
+
return `/**
|
|
260
|
+
* ${options.title ?? '项目自带图标字形表'}。
|
|
261
|
+
*
|
|
262
|
+
* **由 \`icon-from-svg\` 从 SVG 生成,请勿手改** —— 设计稿改了重新生成一次即可。
|
|
263
|
+
* 片段口径:24 视口、单色 \`currentColor\`、坐标不重写(视口换算靠包装层 \`transform\`)。
|
|
264
|
+
*
|
|
265
|
+
* 消费方式 —— 片段直接放进 \`<Icon>\` 的插槽(见 \`@manohub/kit/CONTRACT.md\` L1.5-6 第 ③ 类):
|
|
266
|
+
*
|
|
267
|
+
* \`\`\`tsx
|
|
268
|
+
* import { Icon } from '@manohub/icon'
|
|
269
|
+
* import { ${constName} } from './<本文件>'
|
|
270
|
+
*
|
|
271
|
+
* <Icon>{${constName}['aip-foo']}</Icon>
|
|
272
|
+
* \`\`\`
|
|
273
|
+
*/
|
|
274
|
+
export const ${constName} = {
|
|
275
|
+
${rows}
|
|
276
|
+
} as const
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
/* ============================ 查重 ============================ */
|
|
280
|
+
/**
|
|
281
|
+
* 同一图形的两条条目(命令序列逐字相同 + 所有点位移一致 ⇒ 只是画板里挪了位置)。
|
|
282
|
+
*
|
|
283
|
+
* 设计稿常对多个菜单位复用同一图形(本仓 2026-10-09 就抓到过一处:`server` vs 已删的 `publish`),
|
|
284
|
+
* 所以生成时要顺带报出来 —— 同形只留一条,另一处直接复用同名字形。
|
|
285
|
+
*/
|
|
286
|
+
export function findDuplicatePairs(entries) {
|
|
287
|
+
// 只取路径 `d` 里的坐标:整串里的数字(如包装层的 `scale(1.5)`)会让点位错配,查重就失效了
|
|
288
|
+
const svgPaths = (s) => [...s.matchAll(/ d="([^"]+)"/g)].map((m) => m[1]);
|
|
289
|
+
const cmds = (s) => svgPaths(s).map((p) => (p.match(/[A-Za-z]/g) ?? []).join('')).join('|');
|
|
290
|
+
const nums = (s) => svgPaths(s).flatMap((p) => (p.match(/-?\d*\.?\d+/g) ?? []).map(Number));
|
|
291
|
+
const info = entries.map((e) => ({ name: e.name, c: cmds(e.fragment), v: nums(e.fragment) }));
|
|
292
|
+
const out = [];
|
|
293
|
+
for (let i = 0; i < info.length; i++) {
|
|
294
|
+
for (let j = i + 1; j < info.length; j++) {
|
|
295
|
+
const a = info[i];
|
|
296
|
+
const b = info[j];
|
|
297
|
+
if (a.c !== b.c || a.v.length !== b.v.length || !a.v.length)
|
|
298
|
+
continue;
|
|
299
|
+
const deltas = new Set();
|
|
300
|
+
for (let k = 0; k + 1 < a.v.length; k += 2) {
|
|
301
|
+
deltas.add(`(${num(b.v[k] - a.v[k])}, ${num(b.v[k + 1] - a.v[k + 1])})`);
|
|
302
|
+
}
|
|
303
|
+
if (deltas.size === 1)
|
|
304
|
+
out.push({ a: a.name, b: b.name, delta: [...deltas][0] });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manohub/icon",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "AIHub 图标包:内置 SVG 图标内容表 + 纯字符串渲染器 + <Icon> 组件。零运行时依赖(仅 peer vue),供任意 UI 栈消费",
|
|
@@ -17,8 +17,12 @@
|
|
|
17
17
|
"sideEffects": false,
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
|
+
"bin",
|
|
20
21
|
"README.md"
|
|
21
22
|
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"icon-from-svg": "bin/icon-from-svg.mjs"
|
|
25
|
+
},
|
|
22
26
|
"exports": {
|
|
23
27
|
".": {
|
|
24
28
|
"types": "./dist/index.d.ts",
|
|
@@ -27,6 +31,10 @@
|
|
|
27
31
|
"./glyphs": {
|
|
28
32
|
"types": "./dist/glyphs.d.ts",
|
|
29
33
|
"import": "./dist/glyphs.js"
|
|
34
|
+
},
|
|
35
|
+
"./svg": {
|
|
36
|
+
"types": "./dist/svg.d.ts",
|
|
37
|
+
"import": "./dist/svg.js"
|
|
30
38
|
}
|
|
31
39
|
},
|
|
32
40
|
"main": "./dist/index.js",
|