Skip to content

Commit 68e1973

Browse files
committed
feat(remove-selector): add used/unused api
1 parent d9e5b6c commit 68e1973

File tree

9 files changed

+402
-141
lines changed

9 files changed

+402
-141
lines changed

packages/postcss-plugin-remove-selector/README.md

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,47 @@
2323
pnpm add @novlan/postcss-plugin-remove-selector -D
2424
```
2525

26-
`vite.config.ts` 中使用:
26+
### 简化模式(推荐)
27+
28+
通过 `mode` 指定预设,只需关注 `used`/`unused`,无需手动配置 `file``selectorPattern`
29+
30+
```ts
31+
import { defineConfig } from 'vite';
32+
import { postcssPluginRemoveSelector } from '@novlan/postcss-plugin-remove-selector';
33+
34+
export default defineConfig({
35+
css: {
36+
postcss: {
37+
plugins: [
38+
postcssPluginRemoveSelector({
39+
mode: 'tdesign',
40+
used: ['home', 'chat', 'user', 'add', 'search', 'close'],
41+
}),
42+
],
43+
},
44+
},
45+
});
46+
```
47+
48+
使用 `customUsed` / `customUnused` 可在预设基础上增量追加,不会覆盖预设中已有的列表:
49+
50+
```ts
51+
postcssPluginRemoveSelector({
52+
mode: 'tdesign',
53+
// 在 tdesign 预设默认的 used 列表上,额外追加 'star' 和 'heart'
54+
customUsed: ['star', 'heart'],
55+
// 从结果中额外移除 'loading'
56+
customUnused: ['loading'],
57+
})
58+
```
59+
60+
### 标准模式
61+
62+
通过 `list` 数组传入完整配置,适用于需要匹配多个文件的复杂场景:
2763

2864
```ts
2965
import { defineConfig } from 'vite';
30-
import { postCssPluginRemoveSelector } from '@novlan/postcss-plugin-remove-selector';
66+
import { postcssPluginRemoveSelector } from '@novlan/postcss-plugin-remove-selector';
3167
import {
3268
TDESIGN_ICON_REMOVE_SELECTOR
3369
} from '@novlan/postcss-plugin-remove-selector/lib/tdesign-uniapp-icon';
@@ -36,15 +72,32 @@ import {
3672
export default defineConfig({
3773
css: {
3874
postcss: {
39-
plugins: [postCssPluginRemoveSelector(TDESIGN_ICON_REMOVE_SELECTOR)],
75+
plugins: [postcssPluginRemoveSelector(TDESIGN_ICON_REMOVE_SELECTOR)],
4076
},
4177
},
4278
});
4379
```
4480

4581
## 3. 类型
4682

47-
### Options
83+
插件支持两种配置方式,传入 `SimpleOptions`(简化模式)或 `Options`(标准模式)均可。
84+
85+
### SimpleOptions(简化模式)
86+
87+
| 属性 | 类型 | 是否必填 | 说明 |
88+
| --- | --- | --- | --- |
89+
| `mode` | `'tdesign'` || 预设模式,设置后自动使用对应的 `file``selectorPattern` 默认值 |
90+
| `file` | `RegExp \| string` || 文件匹配规则。使用 `mode` 时可省略 |
91+
| `used` | `string[]` || 正在使用的图标名称列表,这些图标会被保留 |
92+
| `unused` | `string[]` || 未使用的图标名称列表,这些图标会被移除 |
93+
| `customUsed` | `string[]` || 增量追加到 `used` 列表(不覆盖预设或已有的 `used`|
94+
| `customUnused` | `string[]` || 增量追加到 `unused` 列表(不覆盖预设或已有的 `unused`|
95+
| `selectorPattern` | `RegExp` || 选择器匹配模式。使用 `mode` 时可省略 |
96+
| `debug` | `boolean` || 是否开启调试模式 |
97+
98+
> `mode``file` 至少需要指定一个。当 `mode``file`/`selectorPattern` 同时指定时,`file`/`selectorPattern` 优先。
99+
100+
### Options(标准模式)
48101

49102
| 属性 | 类型 | 是否必填 | 说明 |
50103
| --- | --- | --- | --- |
@@ -56,10 +109,18 @@ export default defineConfig({
56109
| 属性 | 类型 | 是否必填 | 说明 |
57110
| --- | --- | --- | --- |
58111
| `file` | `RegExp \| string` || 文件匹配规则,可以是字符串或正则表达式 |
59-
| `include` | `string[]` || 需要保留的选择器列表(图标名称) |
60-
| `exclude` | `string[]` || 需要移除的选择器列表(图标名称) |
112+
| `used` | `string[]` || 正在使用的图标名称列表,这些图标会被保留 |
113+
| `unused` | `string[]` || 未使用的图标名称列表,这些图标会被移除 |
114+
| `customUsed` | `string[]` || 增量追加到 `used` 列表(不覆盖已有的 `used`|
115+
| `customUnused` | `string[]` || 增量追加到 `unused` 列表(不覆盖已有的 `unused`|
61116
| `selectorPattern` | `RegExp` || 选择器匹配模式,只处理匹配该模式的选择器 |
62117

118+
### 内置预设
119+
120+
| mode | 说明 | 默认 file | 默认 selectorPattern |
121+
| --- | --- | --- | --- |
122+
| `tdesign` | TDesign UniApp 图标减包 | `/[@/]tdesign[/]uniapp[/]dist[/]icon[/]icon\.[css\|vue]/` | `/^\.t-icon-[\w-]+:before$/` |
123+
63124
## 4. 更新日志
64125

65126
[点此查看](./CHANGELOG.md)

packages/postcss-plugin-remove-selector/src/helper.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function extractIconName(selector: string): string | null {
3030
* @param options 配置项
3131
*/
3232
export function shouldRemoveRule(options: ShouldRemoveRuleOptions): boolean {
33-
const { selectorPattern, include, exclude, selector } = options;
33+
const { selectorPattern, used, unused, selector } = options;
3434

3535
// 如果配置了 selectorPattern,只处理匹配的选择器
3636
if (selectorPattern && !selectorPattern.test(selector)) {
@@ -40,13 +40,13 @@ export function shouldRemoveRule(options: ShouldRemoveRuleOptions): boolean {
4040
// 提取图标名称进行精确匹配
4141
const iconName = extractIconName(selector);
4242

43-
// 如果有 include 列表,只保留 include 中的图标
44-
if (include.length) {
45-
return !include.includes(iconName || '');
43+
// 如果有 used 列表,只保留 used 中的图标
44+
if (used.length) {
45+
return !used.includes(iconName || '');
4646
}
47-
// 如果有 exclude 列表,移除 exclude 中的图标
48-
if (exclude.length) {
49-
return exclude.includes(iconName || '');
47+
// 如果有 unused 列表,移除 unused 中的图标
48+
if (unused.length) {
49+
return unused.includes(iconName || '');
5050
}
5151
return false;
5252
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './plugin';
22
export * from './tdesign-uniapp-icon';
3+
export * from './presets';
34

4-
export { postCssPluginRemoveSelector as default } from './plugin';
5+
export { postcssPluginRemoveSelector as default } from './plugin';

packages/postcss-plugin-remove-selector/src/plugin.ts

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,78 @@
11
import { shouldHandleFile, shouldRemoveRule } from './helper';
2+
import { PRESETS } from './presets';
23

3-
import type { Options } from './types';
4+
import type { Options, SimpleOptions } from './types';
45

5-
export type { Options, FileConfig, ShouldRemoveRuleOptions } from './types';
6+
export type { Options, SimpleOptions, FileConfig, ShouldRemoveRuleOptions, PresetMode, Preset } from './types';
67
export { shouldHandleFile, shouldRemoveRule, extractIconName } from './helper';
78

89
const PLUGIN_NAME = 'postcss-plugin-remove-selector';
910

11+
/**
12+
* 判断传入的配置是否为简化模式
13+
*/
14+
function isSimpleOptions(opts: Options | SimpleOptions): opts is SimpleOptions {
15+
return !('list' in opts);
16+
}
17+
18+
/**
19+
* 将简化配置转换为标准 Options
20+
*/
21+
function normalizeOptions(opts: Options | SimpleOptions): Options {
22+
if (!isSimpleOptions(opts)) {
23+
return opts;
24+
}
25+
26+
const { mode, file, used, unused, customUsed, customUnused, selectorPattern, debug } = opts;
27+
28+
let resolvedFile = file;
29+
let resolvedSelectorPattern = selectorPattern;
30+
let resolvedUsed = used || [];
31+
let resolvedUnused = unused || [];
32+
33+
// 如果指定了 mode,使用预设的默认值
34+
if (mode && PRESETS[mode]) {
35+
const preset = PRESETS[mode];
36+
if (!resolvedFile) {
37+
resolvedFile = preset.file;
38+
}
39+
if (!resolvedSelectorPattern) {
40+
resolvedSelectorPattern = preset.selectorPattern;
41+
}
42+
// 如果用户没有显式指定 used/unused,使用预设的默认值
43+
if (!used && preset.used) {
44+
resolvedUsed = [...preset.used];
45+
}
46+
if (!unused && preset.unused) {
47+
resolvedUnused = [...preset.unused];
48+
}
49+
}
50+
51+
// 将 customUsed/customUnused 增量追加(不覆盖)
52+
if (customUsed?.length) {
53+
resolvedUsed = [...resolvedUsed, ...customUsed];
54+
}
55+
if (customUnused?.length) {
56+
resolvedUnused = [...resolvedUnused, ...customUnused];
57+
}
58+
59+
if (!resolvedFile) {
60+
throw new Error(`[${PLUGIN_NAME}] 必须指定 "file" 或 "mode",当前均未设置。`);
61+
}
62+
63+
return {
64+
list: [
65+
{
66+
file: resolvedFile,
67+
used: resolvedUsed,
68+
unused: resolvedUnused,
69+
selectorPattern: resolvedSelectorPattern,
70+
},
71+
],
72+
debug,
73+
};
74+
}
75+
1076
/**
1177
* 核心处理逻辑,PostCSS 7 / 8 共用
1278
*/
@@ -19,7 +85,12 @@ function processRoot(root: any, result: any, opts: Options) {
1985
return;
2086
}
2187

22-
const { exclude = [], include = [], selectorPattern } = found;
88+
const { used = [], unused = [], customUsed = [], customUnused = [], selectorPattern } = found;
89+
90+
// 合并 customUsed/customUnused 到 used/unused
91+
const mergedUsed = customUsed.length ? [...used, ...customUsed] : used;
92+
const mergedUnused = customUnused.length ? [...unused, ...customUnused] : unused;
93+
2394
if (debug) {
2495
console.log('[postcss-plugin-remove-selector] handling:', fileName);
2596
}
@@ -29,8 +100,8 @@ function processRoot(root: any, result: any, opts: Options) {
29100
root.walkRules((rule: any) => {
30101
if (shouldRemoveRule({
31102
selectorPattern,
32-
exclude,
33-
include,
103+
used: mergedUsed,
104+
unused: mergedUnused,
34105
selector: rule.selector,
35106
})) {
36107
rule.remove();
@@ -51,36 +122,41 @@ function processRoot(root: any, result: any, opts: Options) {
51122
* - PostCSS 8:使用标准 Creator 函数格式 + postcss: true 标记
52123
* - PostCSS 7:回退到 postcss.plugin() 注册方式
53124
*
125+
* 支持两种配置方式:
126+
* 1. 标准模式:传入 { list: [...], debug?: boolean }
127+
* 2. 简化模式:传入 { mode?, file?, used?, unused?, customUsed?, customUnused?, selectorPattern?, debug? }
128+
*
54129
* @param opts 配置项
55130
* @returns PostCSS 插件
56131
*/
57-
const postCssPluginRemoveSelector: any = (opts: Options = { list: [] }) =>
58-
// PostCSS 8 格式
59-
({
132+
const postcssPluginRemoveSelector: any = (opts: Options | SimpleOptions = { list: [] }) => {
133+
const normalizedOpts = normalizeOptions(opts);
134+
return {
60135
postcssPlugin: PLUGIN_NAME,
61136
Once(root: any, { result }: any) {
62-
processRoot(root, result, opts);
137+
processRoot(root, result, normalizedOpts);
63138
},
64-
})
65-
;
139+
};
140+
};
66141

67142
// 标记为 PostCSS 8 插件
68-
postCssPluginRemoveSelector.postcss = true as const;
143+
postcssPluginRemoveSelector.postcss = true as const;
69144

70145
// PostCSS 7 兼容:通过 postcss.plugin() 注册
71146
try {
72147
// eslint-disable-next-line @typescript-eslint/no-require-imports
73148
const postcss = require('postcss');
74149
if (postcss && typeof postcss.plugin === 'function') {
75-
postCssPluginRemoveSelector.postcss7 = postcss.plugin(
150+
postcssPluginRemoveSelector.postcss7 = postcss.plugin(
76151
PLUGIN_NAME,
77-
(opts: Options = { list: [] }) => (root: any, result: any) => {
78-
processRoot(root, result, opts);
152+
(opts: Options | SimpleOptions = { list: [] }) => (root: any, result: any) => {
153+
const normalizedOpts = normalizeOptions(opts);
154+
processRoot(root, result, normalizedOpts);
79155
},
80156
);
81157
}
82158
} catch (e) {
83159
// postcss 未安装或不支持 postcss.plugin,忽略
84160
}
85161

86-
export { postCssPluginRemoveSelector };
162+
export { postcssPluginRemoveSelector };
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import type { Preset, PresetMode } from './types';
2+
3+
const TDESIGN_USED_ICONS = [
4+
// custom-tab-bar.vue
5+
'home',
6+
'chat',
7+
'user',
8+
9+
// home/index.vue
10+
'add',
11+
12+
// release/index.vue
13+
'location',
14+
'file-copy',
15+
'upload',
16+
17+
// search/index.vue
18+
'search',
19+
'delete',
20+
21+
// data-center/index.vue
22+
'info-circle-filled',
23+
24+
// my/index.vue - 静态图标
25+
'discount',
26+
'edit',
27+
// my/index.vue - gridList 动态图标
28+
'root-list',
29+
// my/index.vue - settingList 动态图标
30+
'service',
31+
'setting',
32+
33+
// setting/index.vue - menuData 动态图标
34+
'app',
35+
'notification',
36+
'image',
37+
'chart',
38+
'sound',
39+
'secured',
40+
'info-circle',
41+
42+
// login/login.vue
43+
'logo-wechat-stroke',
44+
'logo-qq',
45+
'logo-wecom',
46+
'caret-down-small',
47+
48+
// nav-bar.vue
49+
'view-list',
50+
51+
// 组件内部可能使用的图标(如 t-navbar left-arrow 等)
52+
'chevron-left',
53+
'chevron-right',
54+
'chevron-up',
55+
'chevron-down',
56+
'arrow-left',
57+
'arrow-right',
58+
'arrow-up',
59+
'arrow-down',
60+
'close',
61+
'close-circle-filled',
62+
'check',
63+
'check-circle-filled',
64+
'error-circle-filled',
65+
'loading',
66+
];
67+
68+
69+
/**
70+
* 内置预设配置
71+
*
72+
* 每个预设包含默认的 file 和 selectorPattern,
73+
* 用户只需提供 include/exclude 即可快速使用
74+
*/
75+
export const PRESETS: Record<PresetMode, Preset> = {
76+
tdesign: {
77+
/** 匹配 @tdesign/uniapp 的图标 css 文件 */
78+
file: /[@/]tdesign[/]uniapp[/]dist[/]icon[/]icon\.[css|vue]/,
79+
/** 只处理 .t-icon-xxx:before 这类图标选择器 */
80+
selectorPattern: /^\.t-icon-[\w-]+:before$/,
81+
// 保留的图标名称列表
82+
used: TDESIGN_USED_ICONS,
83+
},
84+
};

0 commit comments

Comments
 (0)