-
Notifications
You must be signed in to change notification settings - Fork 284
feat: add plugin to replace icons #2671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b64eee1
d338950
f3d6ec4
cf04a7a
2660c41
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
types | ||
node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
## 功能说明 | ||
|
||
用于替换组件库内置的 Icon 库。 | ||
|
||
## 使用方法 | ||
|
||
### Taro 生态下 | ||
|
||
1. Taro 环境中需要在 config/index.js 文件中增加如下代码 | ||
|
||
```html | ||
{ h5: { compile: { include: [path.resolve(__dirname, '../node_modules')], } }, | ||
mini: { compile: { include: [path.resolve(__dirname, '../node_modules')], } } } | ||
``` | ||
|
||
2. 在 babel.config.js 文件中增加如下代码 | ||
|
||
```js | ||
const { repleaceIcons } = require('@nutui/replace-icons') | ||
{ | ||
plugins: [ | ||
[ | ||
repleaceIcons({ | ||
targetIconLibary: '@test/aa', | ||
iconMap: { | ||
Loading: Star, | ||
}, | ||
}), | ||
], | ||
] | ||
} | ||
``` | ||
|
||
### H5 生态下 | ||
|
||
1. 需要将 nutui 组件库包含在编译中。 | ||
|
||
### 原始代码 | ||
|
||
```jsx | ||
import { Loading } from '@nutui/icons-react' | ||
|
||
export default () => { | ||
return <Loading /> | ||
} | ||
``` | ||
|
||
### 替换后代码 | ||
|
||
```jsx | ||
import { Star as Loading } from '@test/aa' | ||
|
||
export default () => { | ||
return <Loading /> | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"name": "@nutui/replace-icons", | ||
"version": "1.0.2", | ||
"description": "", | ||
"keywords": [ | ||
"Taro", | ||
"nutui", | ||
"nutui react", | ||
"nutui icons", | ||
"Plugin" | ||
], | ||
"publishConfig": { | ||
"access": "public", | ||
"registry": "https://registry.npmjs.org/" | ||
}, | ||
"author": "", | ||
"homepage": "", | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"typings": "types/index.d.ts", | ||
"files": [ | ||
"dist", | ||
"index.js", | ||
"types" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "" | ||
}, | ||
"scripts": { | ||
"build": "rollup -c", | ||
"dev": "rollup -c -w", | ||
"test": "vitest" | ||
}, | ||
"bugs": { | ||
"url": "" | ||
}, | ||
"dependencies": { | ||
"@babel/cli": "^7.25.7", | ||
"@babel/core": "^7.23.9", | ||
"@babel/generator": "^7.24.5", | ||
"@babel/preset-env": "^7.25.7", | ||
"@babel/preset-react": "^7.25.7", | ||
"@types/babel__core": "^7.20.5", | ||
"@types/babel__generator": "^7.6.8", | ||
"vitest": "^1.5.0" | ||
}, | ||
"devDependencies": { | ||
"@types/lodash.kebabcase": "^4.1.9", | ||
"@types/node": "^18.13.0", | ||
"prettier": "^3.2.5", | ||
"rollup": "^2.79.0", | ||
"rollup-plugin-ts": "^3.0.2", | ||
"typescript": "^5.4.5" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const path = require('path') | ||
const ts = require('rollup-plugin-ts') | ||
|
||
const cwd = __dirname | ||
|
||
const base = { | ||
external: ['@tarojs/service'], | ||
plugins: [ts()], | ||
} | ||
|
||
// 供 CLI 编译时使用的 Taro 插件入口 | ||
const compileConfig = { | ||
input: path.join(cwd, 'src/index.ts'), | ||
output: { | ||
file: path.join(cwd, 'dist/index.js'), | ||
format: 'cjs', | ||
sourcemap: true, | ||
}, | ||
...base, | ||
} | ||
|
||
module.exports = [compileConfig] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { replaceIcons } from './replace-icons' | ||
|
||
export default replaceIcons |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { IOptions } from './type' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function replace(options: IOptions) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const sourceLibrary = options.sourceLibrary || [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const targetLibrary = options.targetLibrary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
visitor: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ImportDeclaration(path) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (sourceLibrary.indexOf(path.node.source.value) > -1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 替换包名称 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path.node.source.value = targetLibrary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
path.node.specifiers.forEach((specifier) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// 根据 iconMappings 进行替换 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const iconMappings = options.iconMappings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (iconMappings && iconMappings[specifier.imported.name]) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
specifier.imported.name = iconMappings[specifier.imported.name] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+3
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 代码可以进行以下优化 当前实现有以下几点可以改进:
建议按照以下方式重构: function replace(options: IOptions) {
const sourceLibrary = options.sourceLibrary || []
const targetLibrary = options.targetLibrary
return {
visitor: {
ImportDeclaration(path) {
- if (sourceLibrary.indexOf(path.node.source.value) > -1) {
+ if (sourceLibrary.includes(path.node.source.value)) {
- // 替换包名称
+ // Replace package name
path.node.source.value = targetLibrary
path.node.specifiers.forEach((specifier) => {
- // 根据 iconMappings 进行替换
+ // Replace based on iconMappings
const iconMappings = options.iconMappings
- if (iconMappings && iconMappings[specifier.imported.name]) {
+ if (iconMappings?.[specifier.imported.name]) {
specifier.imported.name = iconMappings[specifier.imported.name]
+ } else {
+ console.warn(`No mapping found for icon: ${specifier.imported.name}`)
}
})
}
},
},
}
} 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome[error] 15-16: Change to an optional chain. Unsafe fix: Change to an optional chain. (lint/complexity/useOptionalChain) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export function replaceIcons( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options: IOptions = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
sourceLibrary: ['@nutui/icons-react', '@nutui/icons-react-taro'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
targetLibrary: '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!options.targetLibrary) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return replace(options) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+25
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 建议增强输入验证和错误处理 当前实现在参数验证和错误处理方面较为简单,建议增加以下功能:
建议修改如下: export function replaceIcons(
options: IOptions = {
sourceLibrary: ['@nutui/icons-react', '@nutui/icons-react-taro'],
targetLibrary: '',
}
) {
+ // Validate sourceLibrary
+ if (!Array.isArray(options.sourceLibrary) || options.sourceLibrary.length === 0) {
+ throw new Error('sourceLibrary must be a non-empty array')
+ }
+
+ // Validate targetLibrary
if (!options.targetLibrary) {
- return {}
+ throw new Error('targetLibrary is required')
}
+
+ // Validate iconMappings if provided
+ if (options.iconMappings && typeof options.iconMappings !== 'object') {
+ throw new Error('iconMappings must be an object')
+ }
+
return replace(options)
} 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,5 @@ | ||||||||||
export interface IOptions { | ||||||||||
sourceLibrary: string[] | ||||||||||
targetLibrary: string | ||||||||||
Comment on lines
+2
to
+3
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 考虑支持多个目标图标库 目前 建议修改如下: export interface IOptions {
sourceLibrary: string[]
- targetLibrary: string
+ targetLibrary: string[]
iconMappings?: { [key: string]: string }
} 📝 Committable suggestion
Suggested change
|
||||||||||
iconMappings?: { [key: string]: string } | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`replace Loading icons with Star 1`] = ` | ||
"import { Star as Loading } from "@test/aa"; | ||
import { ArrowSize6 as Arrow } from "@test/aa"; | ||
const ReplaceOne = () => { | ||
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Loading, null), " ", /*#__PURE__*/React.createElement(Arrow, null)); | ||
};" | ||
`; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,29 @@ | ||||||||||||||||||||||||||||||||||||||
import babel from '@babel/core' | ||||||||||||||||||||||||||||||||||||||
import { describe, expect, it } from 'vitest' | ||||||||||||||||||||||||||||||||||||||
import { replaceIcons } from '../src/replace-icons' | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const plugin = replaceIcons({ | ||||||||||||||||||||||||||||||||||||||
targetLibrary: '@test/aa', | ||||||||||||||||||||||||||||||||||||||
iconMappings: { | ||||||||||||||||||||||||||||||||||||||
Loading: 'Star', | ||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+5
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 建议添加类型定义以增强类型安全性 建议为配置对象添加接口定义,可以防止配置错误并提供更好的开发体验。 +interface IconConfig {
+ targetLibrary: string;
+ iconMappings: Record<string, string>;
+}
-const plugin = replaceIcons({
+const plugin = replaceIcons<IconConfig>({
targetLibrary: '@test/aa',
iconMappings: {
Loading: 'Star',
},
}) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
const babelOptions = { | ||||||||||||||||||||||||||||||||||||||
presets: ['@babel/preset-react'], | ||||||||||||||||||||||||||||||||||||||
plugins: [plugin], | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
const caseIns = ` | ||||||||||||||||||||||||||||||||||||||
import { Loading } from '@nutui/icons-react' | ||||||||||||||||||||||||||||||||||||||
import { ArrowSize6 as Arrow } from '@nutui/icons-react' | ||||||||||||||||||||||||||||||||||||||
const ReplaceOne = () => { | ||||||||||||||||||||||||||||||||||||||
return <><Loading /> <Arrow /></> | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
` | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+16
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 建议改进测试用例的可维护性 建议将测试用例代码提取为单独的常量或文件,这样可以更好地管理多个测试用例。 +const TEST_CASES = {
+ basicReplacement: `
import { Loading } from '@nutui/icons-react'
import { ArrowSize6 as Arrow } from '@nutui/icons-react'
const ReplaceOne = () => {
return <><Loading /> <Arrow /></>
}
+ `,
+};
-const caseIns = \`...\`
+const caseIns = TEST_CASES.basicReplacement; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
describe('', () => { | ||||||||||||||||||||||||||||||||||||||
it('replace Loading icons with Star', () => { | ||||||||||||||||||||||||||||||||||||||
const ast = babel.transformSync(caseIns, babelOptions) | ||||||||||||||||||||||||||||||||||||||
// @ts-ignore | ||||||||||||||||||||||||||||||||||||||
expect(ast.code).toMatchSnapshot() | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+23
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 测试实现需要多处改进
建议按如下方式修改: -describe('', () => {
+describe('replaceIcons Plugin', () => {
it('replace Loading icons with Star', () => {
const ast = babel.transformSync(caseIns, babelOptions)
- // @ts-ignore
- expect(ast.code).toMatchSnapshot()
+ const result = ast?.code ?? '';
+ expect(result).toMatchSnapshot()
+ // 添加具体断言
+ expect(result).toContain('@test/aa')
+ expect(result).toContain('Star')
+ expect(result).not.toContain('Loading')
})
}) 📝 Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compilerOptions": { | ||
"allowSyntheticDefaultImports": true, | ||
"experimentalDecorators": true, | ||
"moduleResolution": "node", | ||
"noImplicitAny": false, | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": true, | ||
"removeComments": false, | ||
"resolveJsonModule": true, | ||
"skipLibCheck": true, | ||
"strictNullChecks": true, | ||
"target": "ES2015", | ||
"outDir": "./dist", | ||
"rootDir": "./src", | ||
"module": "ESNext", | ||
"sourceMap": true, | ||
"declaration": true, | ||
"declarationDir": "types", | ||
"isolatedModules": false, | ||
"types": ["node"] | ||
}, | ||
"include": [ | ||
"./src" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
H5环境配置说明不完整
H5环境的配置说明过于简单,缺少具体的配置步骤和示例代码。建议补充:
需要我帮您补充H5环境的详细配置说明吗?