miaoda-expo-devkit 0.1.1-beta.5 → 0.1.1-beta.51
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 +256 -7
- package/biome-config.json +24 -1
- package/dist/babel/plugin-jsx-source.d.ts +2 -0
- package/dist/babel/plugin-jsx-source.js +12 -3
- package/dist/babel/plugin-lucide-react-native.d.ts +31 -0
- package/dist/babel/plugin-lucide-react-native.js +14790 -0
- package/dist/babel/preset.d.ts +8 -0
- package/dist/babel/preset.js +7 -5
- package/dist/cli/lint.js +25 -11
- package/dist/metro.d.mts +332 -111
- package/dist/metro.d.ts +332 -111
- package/dist/metro.js +427 -98
- package/dist/metro.mjs +416 -99
- package/dist/rules/no-duplicate-expo-router-url.js +17 -15
- package/dist/rules/no-expo-video-compat.js +2041 -0
- package/dist/rules/no-inline-box-shadow-string.js +56 -0
- package/dist/rules/no-invalid-tabs-screen.js +193 -0
- package/dist/rules/no-missing-css-import.js +100 -0
- package/dist/rules/no-pressable-without-on-press.js +77 -0
- package/dist/rules/no-undeclared-expo-plugin.js +34 -12
- package/dist/rules/no-unregistered-dynamic-tab-route.js +183 -0
- package/dist/rules/no-unused-expo-plugin.js +164 -0
- package/dist/stubs/expo-blur-stub.js +29 -0
- package/dist/stubs/expo-calendar-stub.js +353 -0
- package/dist/stubs/expo-file-system-stub.js +214 -0
- package/dist/stubs/expo-image-picker-stub.js +241 -0
- package/dist/stubs/expo-linear-gradient-stub.js +28 -0
- package/dist/stubs/expo-media-library-stub.js +141 -0
- package/dist/stubs/expo-notifications-stub.js +177 -0
- package/dist/stubs/lgui-control.js +104 -12
- package/dist/stubs/web-stub-dialog.js +168 -0
- package/oxlint-config.json +30 -1
- package/package.json +61 -30
- package/pnpm-config.json +1 -6
- package/tsconfig-base.json +7 -0
package/README.md
CHANGED
|
@@ -7,6 +7,10 @@ Expo / React Native 开发环境工具集,通过 Metro 构建层注入以下
|
|
|
7
7
|
- **Bundle 首部注入** — 在 expo-router 初始化之前执行自定义脚本
|
|
8
8
|
- **HMR postMessage 控制** — 通过 `window.postMessage` 在运行时启动或停止 Fast Refresh
|
|
9
9
|
- **LogBox 屏蔽** — web 平台禁用 Expo 全屏错误遮罩
|
|
10
|
+
- **expo-notifications stub** — Expo Go(Android)中提供 no-op 实现,核心 API 调用时弹出带参数校验的调试 Alert,Dev Build 透传真实模块
|
|
11
|
+
- **expo-media-library stub** — Expo Go / Web 中提供 no-op 实现,`saveToLibraryAsync`、`createAssetAsync`、权限请求等 API 调用时弹出 Alert 提示,Dev Build 原生环境透传真实模块
|
|
12
|
+
- **expo-calendar stub** — Expo Go / Web 中提供 no-op 实现,`getEventsAsync`、`createEventAsync`、`getCalendarsAsync`、权限请求等核心 API 调用时弹出 Alert 提示并校验参数,Dev Build 原生环境透传真实模块
|
|
13
|
+
- **expo-image-picker stub** — 桌面 Web 中 `launchCameraAsync` 通过 `getUserMedia` 打开摄像头预览弹窗(浏览器原生 `capture` 属性在 PC 端被忽略),移动端浏览器透传 expo 原实现,Native 不受影响
|
|
10
14
|
|
|
11
15
|
## 安装
|
|
12
16
|
|
|
@@ -32,16 +36,16 @@ pnpm install
|
|
|
32
36
|
```js
|
|
33
37
|
// metro.config.js
|
|
34
38
|
const { getDefaultConfig } = require('expo/metro-config');
|
|
35
|
-
const {
|
|
39
|
+
const { withDevkit } = require('miaoda-expo-devkit/metro');
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
module.exports = withEntryInjection(withDevStubs(config));
|
|
41
|
+
module.exports = withDevkit(getDefaultConfig(__dirname));
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
`withDevkit` 已内置所有 wrapper(含 expo-notifications、expo-media-library、expo-calendar stub),无需手动叠加。也可单独使用各 wrapper:
|
|
42
45
|
|
|
43
46
|
```js
|
|
44
|
-
|
|
47
|
+
const { withDevStubs, withEntryInjection, withExpoMediaLibraryStub, withExpoCalendarStub } = require('miaoda-expo-devkit/metro');
|
|
48
|
+
module.exports = withExpoCalendarStub(withExpoMediaLibraryStub(withEntryInjection(withDevStubs(config))));
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
### Sentry 初始化
|
|
@@ -225,12 +229,35 @@ expect(onError).toHaveBeenCalledWith(
|
|
|
225
229
|
|
|
226
230
|
```
|
|
227
231
|
metro.config.js
|
|
228
|
-
└─
|
|
232
|
+
└─ withDevkit(config)
|
|
229
233
|
│
|
|
230
234
|
├─ withDevStubs → resolver.resolveRequest
|
|
231
235
|
│ ├─ @sentry/react-native → dist/stubs/sentry-react-native-stub.js (全平台)
|
|
232
236
|
│ └─ @expo/log-box → dist/stubs/no-op-logbox.js (仅 web)
|
|
233
237
|
│
|
|
238
|
+
├─ withExpoNotificationsStub → resolver.resolveRequest
|
|
239
|
+
│ └─ expo-notifications → dist/stubs/expo-notifications-stub.js (仅 Android)
|
|
240
|
+
│ ├─ Expo Go:no-op + 调试 Alert(含参数校验)
|
|
241
|
+
│ └─ Dev Build:透传真实 expo-notifications
|
|
242
|
+
│
|
|
243
|
+
├─ withExpoMediaLibraryStub → resolver.resolveRequest
|
|
244
|
+
│ └─ expo-media-library → dist/stubs/expo-media-library-stub.js (全平台)
|
|
245
|
+
│ ├─ Expo Go / Web:no-op + Alert 提示(不崩溃)
|
|
246
|
+
│ └─ Dev Build(原生):透传真实 expo-media-library
|
|
247
|
+
│
|
|
248
|
+
├─ withExpoCalendarStub → resolver.resolveRequest
|
|
249
|
+
│ └─ expo-calendar → dist/stubs/expo-calendar-stub.js (全平台)
|
|
250
|
+
│ ├─ Expo Go / Web:no-op + Alert 提示 + 参数校验(不崩溃)
|
|
251
|
+
│ └─ Dev Build(原生):透传真实 expo-calendar
|
|
252
|
+
│
|
|
253
|
+
├─ withExpoImagePickerStub → resolver.resolveRequest
|
|
254
|
+
│ └─ expo-image-picker → dist/stubs/expo-image-picker-stub.js (仅 web)
|
|
255
|
+
│ ├─ 桌面 Web:launchCameraAsync → getUserMedia + #__devkit_camera_overlay__ 弹窗
|
|
256
|
+
│ │ ├─ 点"拍照":canvas.toDataURL → ImagePickerResult { canceled:false, assets }
|
|
257
|
+
│ │ └─ 点"取消":返回 { canceled:true, assets:null }
|
|
258
|
+
│ ├─ 移动端浏览器:透传 expo 原实现(capture 属性正常工作)
|
|
259
|
+
│ └─ getUserMedia 不可用:降级透传 expo 原实现(不崩溃)
|
|
260
|
+
│
|
|
234
261
|
└─ withEntryInjection → resolver.resolveRequest
|
|
235
262
|
└─ expo-router/entry-classic → dist/stubs/expo-router-entry-stub.js
|
|
236
263
|
├─ require('./entry-inject') ← 注入脚本(bundle 首部执行)
|
|
@@ -262,10 +289,15 @@ sentry-react-native-stub.js
|
|
|
262
289
|
| 子路径 | 文件 | 内容 |
|
|
263
290
|
|---|---|---|
|
|
264
291
|
| `.` | `dist/index.js` | `SentryCapture`、`MetroSymbolicator`、全部类型 |
|
|
265
|
-
| `./metro` | `dist/metro.js` | `withDevStubs`、`withEntryInjection` |
|
|
292
|
+
| `./metro` | `dist/metro.js` | `withDevkit`、`withDevStubs`、`withEntryInjection`、`withExpoNotificationsStub`、`withExpoMediaLibraryStub`、`withExpoCalendarStub` 等 |
|
|
266
293
|
| `./babel-plugin-jsx-source` | `dist/babel/plugin-jsx-source.js` | Babel 插件:为 JSX 注入 source 信息 |
|
|
294
|
+
| `./babel-preset` | `dist/babel/preset.js` | Babel Preset:集成 jsx-source 和 lucide 插件 |
|
|
267
295
|
| `./sentry-react-native-stub` | `dist/stubs/sentry-react-native-stub.js` | `@sentry/react-native` 模块替换 stub |
|
|
268
296
|
| `./no-op-logbox` | `dist/stubs/no-op-logbox.js` | LogBox no-op stub |
|
|
297
|
+
| `./expo-notifications-stub` | `dist/stubs/expo-notifications-stub.js` | `expo-notifications` Expo Go Android stub |
|
|
298
|
+
| `./expo-media-library-stub` | `dist/stubs/expo-media-library-stub.js` | `expo-media-library` Expo Go / Web stub |
|
|
299
|
+
| `./expo-calendar-stub` | `dist/stubs/expo-calendar-stub.js` | `expo-calendar` Expo Go / Web stub |
|
|
300
|
+
| `./expo-image-picker-stub` | `dist/stubs/expo-image-picker-stub.js` | `expo-image-picker` 桌面 Web stub |
|
|
269
301
|
|
|
270
302
|
---
|
|
271
303
|
|
|
@@ -388,6 +420,145 @@ LogBox no-op stub,用于 web 平台禁用 Expo 全屏错误遮罩。
|
|
|
388
420
|
|
|
389
421
|
---
|
|
390
422
|
|
|
423
|
+
### expo-notifications-stub.js
|
|
424
|
+
|
|
425
|
+
`expo-notifications` 模块替换 stub,由 `withExpoNotificationsStub()` 在 Metro 层注入(**仅 Android 平台**)。
|
|
426
|
+
|
|
427
|
+
**背景:** Expo SDK 53 起,`expo-notifications` 的 Android native module 已从 Expo Go 中移除,直接 import 会在 Expo Go 启动时崩溃。
|
|
428
|
+
|
|
429
|
+
**运行时行为:**
|
|
430
|
+
- **Expo Go(Android)**:提供 no-op 实现,`requestPermissionsAsync`、`setNotificationChannelAsync`、`scheduleNotificationAsync` 等核心 API 调用时弹出带参数校验的调试 Alert
|
|
431
|
+
- **Development Build(Android)**:透传真实 `expo-notifications`,功能完全正常
|
|
432
|
+
- **iOS(任意)**:不经过此 stub,直接使用真实 `expo-notifications`
|
|
433
|
+
|
|
434
|
+
**手动验证:** 在 `devkit-e2e` App 中扫码进入「Notification Stub 验证」页面,逐按钮触发并对照期望结果。
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
### expo-media-library-stub.js
|
|
439
|
+
|
|
440
|
+
`expo-media-library` 模块替换 stub,由 `withExpoMediaLibraryStub()` 在 Metro 层注入(**全平台**)。
|
|
441
|
+
|
|
442
|
+
**背景:** `expo-media-library` 依赖原生相册 API,在 Expo Go 和 Web 环境中不可用,调用 `saveToLibraryAsync` 等 API 会直接崩溃。
|
|
443
|
+
|
|
444
|
+
**运行时行为:**
|
|
445
|
+
- **Expo Go / Web**:提供 no-op 实现,以下 API 调用时弹出 Alert 提示(不崩溃):
|
|
446
|
+
- `usePermissions()` — 返回 `{ status: 'undetermined', granted: false }`,`requestPermission()` 弹 Alert
|
|
447
|
+
- `requestPermissionsAsync()` / `getPermissionsAsync()` — 前者弹 Alert,后者静默返回 denied
|
|
448
|
+
- `saveToLibraryAsync(uri)` — 弹 Alert 显示操作和 URI(超 60 字符自动截断)
|
|
449
|
+
- `createAssetAsync(uri)` — 弹 Alert 并返回合法的伪资产对象
|
|
450
|
+
- 其他未知 API — Proxy 兜底,静默返回 `undefined`;以 `PermissionsAsync` 结尾的 API 返回 denied 结构
|
|
451
|
+
- **Development Build(原生)**:透传真实 `expo-media-library`,功能完全正常
|
|
452
|
+
|
|
453
|
+
**Alert 消息格式:**
|
|
454
|
+
```
|
|
455
|
+
保存到相册
|
|
456
|
+
Expo Go 扫码预览不支持访问手机相册
|
|
457
|
+
操作: 图片: file:///tmp/test.jpg
|
|
458
|
+
发布为正式 App 后可正常使用
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**手动验证:** 在 `devkit-e2e` App 中扫码进入「Media Library Stub 验证」页面,逐按钮触发并对照期望结果。
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
### expo-calendar-stub.js
|
|
466
|
+
|
|
467
|
+
`expo-calendar` 模块替换 stub,由 `withExpoCalendarStub()` 在 Metro 层注入(**全平台**)。
|
|
468
|
+
|
|
469
|
+
**背景:** `expo-calendar` 依赖原生系统日历 API,在 Expo Go 和 Web 环境中不可用,调用 `getEventsAsync` 等 API 会直接崩溃。
|
|
470
|
+
|
|
471
|
+
**运行时行为:**
|
|
472
|
+
- **Expo Go / Web**:提供 no-op 实现,以下 API 调用时弹出 Alert / Dialog 提示(不崩溃):
|
|
473
|
+
- `useCalendarPermissions()` / `useRemindersPermissions()` — 初始返回 `undetermined`,`requestPermission()` 弹 Alert
|
|
474
|
+
- `requestCalendarPermissionsAsync()` / `requestRemindersPermissionsAsync()` — 弹 Alert 提示,返回 denied
|
|
475
|
+
- `getCalendarPermissionsAsync()` / `getRemindersPermissionsAsync()` — 静默返回 denied
|
|
476
|
+
- `getCalendarsAsync(entityType?)` — 弹 Alert 显示查询类型,返回空数组
|
|
477
|
+
- `createCalendarAsync(details)` — 弹 Alert 含名称和颜色,校验 `details.title` 非空
|
|
478
|
+
- `updateCalendarAsync(id, details)` / `deleteCalendarAsync(id)` — 弹 Alert 含日历 ID,校验 ID 格式
|
|
479
|
+
- `getEventsAsync(calendarIds, startDate, endDate)` — 弹 Alert 含时间范围,校验 calendarIds 非空及日期合法性,返回空数组
|
|
480
|
+
- `createEventAsync(calendarId, eventData)` — 弹 Alert 含标题和时间,Android 下校验 startDate/endDate
|
|
481
|
+
- `updateEventAsync(id, details)` / `deleteEventAsync(id)` — 弹 Alert 含事件 ID,校验 ID 格式
|
|
482
|
+
- 其他未知 API — Proxy 兜底,静默返回 `undefined`;以 `PermissionsAsync` 结尾的 API 返回 denied 结构
|
|
483
|
+
- **Development Build(原生)**:透传真实 `expo-calendar`,功能完全正常
|
|
484
|
+
|
|
485
|
+
**枚举常量**(stub & Dev Build 均可用):
|
|
486
|
+
`EntityTypes`、`Frequency`、`Availability`、`CalendarType`、`EventStatus`、`SourceType`、
|
|
487
|
+
`AttendeeRole`、`AttendeeStatus`、`AttendeeType`、`AlarmMethod`、`EventAccessLevel`、
|
|
488
|
+
`CalendarAccessLevel`、`ReminderStatus`、`DayOfTheWeek`、`MonthOfTheYear`
|
|
489
|
+
|
|
490
|
+
**Alert 消息格式(合规示例):**
|
|
491
|
+
```
|
|
492
|
+
创建日历事件
|
|
493
|
+
秒哒扫码预览不支持访问手机日历
|
|
494
|
+
|
|
495
|
+
日历 ID: cal1
|
|
496
|
+
标题: 团队会议
|
|
497
|
+
开始: 2025/1/1 10:00:00
|
|
498
|
+
结束: 2025/1/1 11:00:00
|
|
499
|
+
|
|
500
|
+
✅ 参数合规
|
|
501
|
+
发布为正式 App 后可正常使用
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**手动验证:** 在 `devkit-e2e` App 中扫码进入「Calendar Stub 验证」页面,逐按钮触发并对照期望结果。
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
### expo-image-picker-stub.js
|
|
511
|
+
|
|
512
|
+
`expo-image-picker` 模块替换 stub,由 `withExpoImagePickerStub()` 在 Metro 层注入(**仅 web 平台**)。
|
|
513
|
+
|
|
514
|
+
**背景:** `expo-image-picker` 的 `launchCameraAsync` 在 web 上底层使用 `<input type="file" capture="environment">`。桌面浏览器(Chrome、Firefox、Safari)**有意忽略 `capture` 属性**,直接弹出文件选择框而不是摄像头。这是浏览器厂商的硬限制,无法通过任何 HTML 属性或 meta 标签绕过。
|
|
515
|
+
|
|
516
|
+
**运行时行为:**
|
|
517
|
+
- **桌面 Web(非 mobile UA)**:`launchCameraAsync` 调用 `navigator.mediaDevices.getUserMedia` 打开摄像头预览弹窗(`#__devkit_camera_overlay__`),用户点击"📷 拍照"后截取一帧并以 `data:image/jpeg` 格式返回,点击"取消"返回 `{ canceled: true }`
|
|
518
|
+
- **移动端浏览器(Android Chrome / iOS Safari)**:透传 expo 原实现,`capture` 属性在移动端正常调起系统相机
|
|
519
|
+
- **`getUserMedia` 不可用**(无摄像头或浏览器限制):静默降级,透传 expo 原实现(不崩溃)
|
|
520
|
+
- **Native(iOS / Android)**:Metro resolver 不拦截,直接使用原生 `expo-image-picker`,功能完全不变
|
|
521
|
+
|
|
522
|
+
**弹窗 DOM 结构(供 Playwright 定位):**
|
|
523
|
+
```html
|
|
524
|
+
<div id="__devkit_camera_overlay__"> <!-- 全屏遮罩 -->
|
|
525
|
+
<div> <!-- 面板 -->
|
|
526
|
+
<p>拍照</p>
|
|
527
|
+
<video autoplay muted playsinline> <!-- 摄像头预览 -->
|
|
528
|
+
<div>
|
|
529
|
+
<button>📷 拍照</button>
|
|
530
|
+
<button>取消</button>
|
|
531
|
+
</div>
|
|
532
|
+
<p><!-- 错误信息(getUserMedia 失败时显示) --></p>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**返回值格式(与 expo 原实现兼容):**
|
|
538
|
+
```ts
|
|
539
|
+
// 拍照成功
|
|
540
|
+
{
|
|
541
|
+
canceled: false,
|
|
542
|
+
assets: [{
|
|
543
|
+
uri: 'data:image/jpeg;base64,...',
|
|
544
|
+
width: 640,
|
|
545
|
+
height: 480,
|
|
546
|
+
type: 'image',
|
|
547
|
+
fileName: 'photo_1234567890.jpg',
|
|
548
|
+
mimeType: 'image/jpeg',
|
|
549
|
+
base64: null,
|
|
550
|
+
exif: null,
|
|
551
|
+
}]
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// 取消
|
|
555
|
+
{ canceled: true, assets: null }
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**E2E 测试:** `devkit-e2e/tests/e2e/image-picker-stub.spec.ts`,使用 `addInitScript` mock `getUserMedia`(注入 canvas stream 替代真实摄像头),覆盖弹窗出现/消失、拍照返回结果、取消、降级等场景。
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
391
562
|
## Babel 插件:jsx-source
|
|
392
563
|
|
|
393
564
|
`babel-plugin-jsx-source` 为 JSX 元素注入 source 属性(文件路径、行列号),用于开发调试。通过 `dataSet` 对象注入,这是 React Web 可识别的数据通道。
|
|
@@ -434,3 +605,81 @@ module.exports = {
|
|
|
434
605
|
|---|---|---|---|
|
|
435
606
|
| `rootDir` | `string` | - | 项目根目录,用于计算相对路径。不提供则使用绝对路径 |
|
|
436
607
|
| `excludePaths` | `string[]` | `[]` | 跳过注入的路径模式列表(相对于 rootDir 的路径片段) |
|
|
608
|
+
|
|
609
|
+
---
|
|
610
|
+
|
|
611
|
+
## Babel 插件:lucide-react-native
|
|
612
|
+
|
|
613
|
+
Metro 没有 tree-shaking,直接写 `import { Star } from "lucide-react-native"` 会把整个图标库(约 1500 个图标)打进 bundle。`babel-plugin-lucide-react-native` 在编译阶段将具名导入改写为直接按文件导入,彻底规避这个问题。
|
|
614
|
+
|
|
615
|
+
**转换效果:**
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
// 转换前
|
|
619
|
+
import { Star, BookOpen } from "lucide-react-native";
|
|
620
|
+
|
|
621
|
+
// 转换后(CJS,默认)
|
|
622
|
+
import _Star from "lucide-react-native/dist/cjs/icons/star";
|
|
623
|
+
import _BookOpen from "lucide-react-native/dist/cjs/icons/book-open";
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### 配置
|
|
627
|
+
|
|
628
|
+
推荐通过内置 Preset 一次性启用所有 Babel 插件:
|
|
629
|
+
|
|
630
|
+
```js
|
|
631
|
+
// babel.config.js
|
|
632
|
+
module.exports = {
|
|
633
|
+
presets: [['miaoda-expo-devkit/babel-preset', { excludePaths: ['src/components/ui'] }]],
|
|
634
|
+
};
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
也可以单独使用:
|
|
638
|
+
|
|
639
|
+
```js
|
|
640
|
+
module.exports = {
|
|
641
|
+
plugins: [['miaoda-expo-devkit/babel-plugin-lucide-react-native']],
|
|
642
|
+
};
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 选项
|
|
646
|
+
|
|
647
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
648
|
+
|---|---|---|---|
|
|
649
|
+
| `useES` | `boolean` | `false` | 使用 ESM 格式(`dist/esm/icons/`)而非默认的 CJS |
|
|
650
|
+
|
|
651
|
+
### 支持的写法
|
|
652
|
+
|
|
653
|
+
插件可识别所有常见的图标引用写法:
|
|
654
|
+
|
|
655
|
+
```ts
|
|
656
|
+
import { BookOpen, Star, Crown } from "lucide-react-native";
|
|
657
|
+
|
|
658
|
+
// JSX 直接使用
|
|
659
|
+
<BookOpen size={24} />
|
|
660
|
+
|
|
661
|
+
// 赋值给变量
|
|
662
|
+
const icon = BookOpen;
|
|
663
|
+
|
|
664
|
+
// 对象 value
|
|
665
|
+
const ITEMS = [{ icon: BookOpen }, { icon: Star }];
|
|
666
|
+
|
|
667
|
+
// 计算属性 key
|
|
668
|
+
const map = { [BookOpen]: 'read' };
|
|
669
|
+
|
|
670
|
+
// 数组、三元、函数参数
|
|
671
|
+
const list = [BookOpen, Star];
|
|
672
|
+
const active = flag ? BookOpen : Star;
|
|
673
|
+
renderIcon(Crown);
|
|
674
|
+
|
|
675
|
+
// 导入别名
|
|
676
|
+
import { BookOpen as ReadIcon } from "lucide-react-native";
|
|
677
|
+
<ReadIcon />
|
|
678
|
+
|
|
679
|
+
// re-export
|
|
680
|
+
export { Star, BookOpen as ReadIcon } from "lucide-react-native";
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### 版本兼容性
|
|
684
|
+
|
|
685
|
+
插件在运行时探测 `lucide-react-native` 的实际目录结构,自动适配新旧版本的路径差异(`>= 1.9` 新增 `icons/` 子目录;旧版 `1.8.x` 图标直接位于 `dist/cjs/`),lucide 升级时无需修改配置。
|
package/biome-config.json
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
+
"vcs": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"clientKind": "git",
|
|
5
|
+
"useIgnoreFile": true
|
|
6
|
+
},
|
|
7
|
+
"files": {
|
|
8
|
+
"includes": [
|
|
9
|
+
"src/**/*.{js,jsx,ts,tsx,css,scss}",
|
|
10
|
+
"tailwind.config.js"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
2
13
|
"linter": {
|
|
3
14
|
"enabled": true,
|
|
4
15
|
"rules": {
|
|
@@ -10,5 +21,17 @@
|
|
|
10
21
|
},
|
|
11
22
|
"formatter": {
|
|
12
23
|
"enabled": false
|
|
13
|
-
}
|
|
24
|
+
},
|
|
25
|
+
"overrides": [
|
|
26
|
+
{
|
|
27
|
+
"includes": ["tailwind.config.js"],
|
|
28
|
+
"linter": {
|
|
29
|
+
"rules": {
|
|
30
|
+
"style": {
|
|
31
|
+
"noCommonJs": "off"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
]
|
|
14
37
|
}
|
|
@@ -14,8 +14,10 @@ import { types, PluginObj } from '@babel/core';
|
|
|
14
14
|
*
|
|
15
15
|
* 对于纯文本节点:
|
|
16
16
|
* <div>hello world</div>
|
|
17
|
+
* <div>{"hello world"}</div>
|
|
17
18
|
* 转换后:
|
|
18
19
|
* <div dataSet={{"mdId": "path/to/file.tsx:10:4", "componentContent": "{\"text\":\"hello world\"}"}}>hello world</div>
|
|
20
|
+
* <div dataSet={{"mdId": "path/to/file.tsx:11:4", "componentContent": "{\"text\":\"hello world\"}"}}>{"hello world"}</div>
|
|
19
21
|
*
|
|
20
22
|
* 用法(babel.config.js):
|
|
21
23
|
*
|
|
@@ -69,9 +69,18 @@ function getTextNodeContent(t, path) {
|
|
|
69
69
|
const children = parent.children;
|
|
70
70
|
if (children.length !== 1) return null;
|
|
71
71
|
const child = children[0];
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (t.isJSXText(child)) {
|
|
73
|
+
const text = child.value.trim();
|
|
74
|
+
return text || null;
|
|
75
|
+
}
|
|
76
|
+
if (t.isJSXExpressionContainer(child) && t.isStringLiteral(child.expression)) {
|
|
77
|
+
const text = child.expression.value.trim();
|
|
78
|
+
return text || null;
|
|
79
|
+
}
|
|
80
|
+
if (t.isJSXExpressionContainer(child) && t.isNumericLiteral(child.expression)) {
|
|
81
|
+
return String(child.expression.value);
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
75
84
|
}
|
|
76
85
|
function shouldSkipElement(t, name) {
|
|
77
86
|
if (t.isJSXIdentifier(name)) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import babelCore from '@babel/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* babel-plugin-lucide-react-native
|
|
5
|
+
*
|
|
6
|
+
* 将 lucide-react-native 的具名导入转换为按图标文件直接导入,绕过 Metro
|
|
7
|
+
* 缺乏 tree-shaking 的问题,避免将整个图标库打进 bundle。
|
|
8
|
+
*
|
|
9
|
+
* 转换示意:
|
|
10
|
+
* import { Star, BookHeart } from "lucide-react-native"
|
|
11
|
+
* →
|
|
12
|
+
* import _Star from "lucide-react-native/dist/cjs/icons/star"
|
|
13
|
+
* import _BookHeart from "lucide-react-native/dist/cjs/icons/book-heart"
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type Core = typeof babelCore;
|
|
17
|
+
interface Config extends babelCore.PluginPass {
|
|
18
|
+
opts: {
|
|
19
|
+
/** 使用 ESM 格式(dist/esm)。默认 false,使用 CJS(dist/cjs)。 */
|
|
20
|
+
useES?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* lucide-react-native 包根目录的绝对路径(含 package.json 的那一层)。
|
|
23
|
+
* 不传时通过 require.resolve 自动定位,适用于生产场景。
|
|
24
|
+
* 测试时可传入特定版本的路径,验证不同版本的兼容性。
|
|
25
|
+
*/
|
|
26
|
+
lucidePkgRoot?: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
declare function lucideReactNativePlugin({ types: t }: Core): babelCore.PluginObj<Config>;
|
|
30
|
+
|
|
31
|
+
export { lucideReactNativePlugin as default };
|