miaoda-expo-devkit 0.1.1-beta.3 → 0.1.1-beta.5
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/dist/cli/lint.js +21 -0
- package/dist/metro.d.mts +65 -3
- package/dist/metro.d.ts +65 -3
- package/dist/metro.js +77 -6
- package/dist/metro.mjs +81 -6
- package/dist/rules/no-duplicate-expo-router-url.js +1 -1
- package/dist/stubs/expo-camera-stub.js +28 -0
- package/dist/stubs/expo-image-stub.js +28 -0
- package/oxlint-config.json +47 -0
- package/package.json +8 -2
package/dist/cli/lint.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli/lint.ts
|
|
5
|
+
var import_node_child_process = require("child_process");
|
|
6
|
+
var import_node_path = require("path");
|
|
7
|
+
var devkitRoot = (0, import_node_path.resolve)(__dirname, "../..");
|
|
8
|
+
var oxlintConfig = (0, import_node_path.join)(devkitRoot, "oxlint-config.json");
|
|
9
|
+
var biomeConfig = (0, import_node_path.join)(devkitRoot, "biome-config.json");
|
|
10
|
+
var targets = process.argv.slice(2);
|
|
11
|
+
var lintTargets = targets.length > 0 ? targets : ["src/"];
|
|
12
|
+
var failed = false;
|
|
13
|
+
var oxlint = (0, import_node_child_process.spawnSync)("oxlint", ["--config", oxlintConfig, ...lintTargets], {
|
|
14
|
+
stdio: "inherit"
|
|
15
|
+
});
|
|
16
|
+
if ((oxlint.status ?? 1) !== 0) failed = true;
|
|
17
|
+
var biome = (0, import_node_child_process.spawnSync)("biome", ["lint", "--config-path", biomeConfig, ...lintTargets], {
|
|
18
|
+
stdio: "inherit"
|
|
19
|
+
});
|
|
20
|
+
if ((biome.status ?? 1) !== 0) failed = true;
|
|
21
|
+
process.exit(failed ? 1 : 0);
|
package/dist/metro.d.mts
CHANGED
|
@@ -84,8 +84,8 @@ interface RouteEndpointOptions {
|
|
|
84
84
|
* 端点返回格式:
|
|
85
85
|
* {
|
|
86
86
|
* "routes": [
|
|
87
|
-
* { "title": "首页", "pageId": "/", "pageName": "index", "visible": true, "path": "index.tsx" },
|
|
88
|
-
* { "title": "id", "pageId": "/user/[id]", "pageName": "user-[id]", "visible": false, "path": "user/[id].tsx" }
|
|
87
|
+
* { "title": "首页", "pageId": "/", "pageName": "index", "visible": true, "path": "src/app/index.tsx" },
|
|
88
|
+
* { "title": "id", "pageId": "/user/[id]", "pageName": "user-[id]", "visible": false, "path": "src/app/user/[id].tsx" }
|
|
89
89
|
* ]
|
|
90
90
|
* }
|
|
91
91
|
*
|
|
@@ -114,5 +114,67 @@ declare function withRouteEndpoint(config: MetroConfig, options: RouteEndpointOp
|
|
|
114
114
|
* @returns 修正 watchFolders 和 nodeModulesPaths 后的新 Metro config
|
|
115
115
|
*/
|
|
116
116
|
declare function withWorkspaceNodeModules(config: MetroConfig): MetroConfig;
|
|
117
|
+
/**
|
|
118
|
+
* 为第三方 Expo 组件注入 NativeWind cssInterop,使其支持 className prop。
|
|
119
|
+
*
|
|
120
|
+
* 注入方式:Metro resolver 层拦截对应包的 import,重定向到 stub 文件。
|
|
121
|
+
* stub 文件透传原包的所有导出,并调用 cssInterop 完成注册。
|
|
122
|
+
* stub 内部 import 原包时,originModulePath 检查避免循环依赖。
|
|
123
|
+
*
|
|
124
|
+
* 当前支持的包:
|
|
125
|
+
* - expo-image:Image 组件(className → style)
|
|
126
|
+
* - expo-camera:CameraView 组件(className → style)
|
|
127
|
+
*
|
|
128
|
+
* 此函数在开发和生产环境均需启用(cssInterop 注册与环境无关)。
|
|
129
|
+
* 支持与其他 Metro wrapper 链式组合。
|
|
130
|
+
*
|
|
131
|
+
* @param config Metro config 对象
|
|
132
|
+
* @returns 注入 resolveRequest 后的新 Metro config
|
|
133
|
+
*/
|
|
134
|
+
declare function withCssInterop(config: MetroConfig): MetroConfig;
|
|
135
|
+
/** patchNativeWindCachePath 的配置选项 */
|
|
136
|
+
interface PatchNativeWindCacheOptions {
|
|
137
|
+
/**
|
|
138
|
+
* 缓存目录,相对于 process.cwd()(Metro 项目根目录),默认 .native-wind-cache。
|
|
139
|
+
* 与 package.json 同级,避免写入只读的 node_modules。
|
|
140
|
+
*/
|
|
141
|
+
cacheDir?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 将 NativeWind 编译缓存从 node_modules/.cache 重定向到项目根目录下的自定义目录。
|
|
145
|
+
*
|
|
146
|
+
* ⚠️ 必须在 require('nativewind/metro') / require('react-native-css-interop/dist/metro')
|
|
147
|
+
* 之前调用,否则 outputDirectory 已经固化,patch 无效。
|
|
148
|
+
*
|
|
149
|
+
* 原理:react-native-css-interop/dist/metro 在模块顶层用
|
|
150
|
+
* const outputDirectory = path.resolve(__dirname, "../../.cache")
|
|
151
|
+
* 计算缓存路径。通过在 require 前临时替换 path.resolve,可将该值重定向到
|
|
152
|
+
* 项目根目录下的自定义缓存目录,而无需 fork 或修改 node_modules 源码。
|
|
153
|
+
*
|
|
154
|
+
* 所有依赖 outputDirectory 的 NativeWind 行为(文件写入、虚拟模块 Map key、
|
|
155
|
+
* Metro resolver 返回值、haste 事件)都会一致使用新路径,无需额外 Metro 层面的 patch。
|
|
156
|
+
*
|
|
157
|
+
* ⚠️ 已知限制:依赖 react-native-css-interop 使用 path.resolve 计算缓存路径,
|
|
158
|
+
* 升级该依赖时需确认此假设仍然成立。
|
|
159
|
+
*
|
|
160
|
+
* 用法(metro.config.js):
|
|
161
|
+
* const { patchNativeWindCachePath } = require('miaoda-expo-devkit/metro');
|
|
162
|
+
* patchNativeWindCachePath(); // 必须在 require nativewind/metro 之前调用
|
|
163
|
+
* const { withNativeWind } = require('nativewind/metro');
|
|
164
|
+
*/
|
|
165
|
+
declare function patchNativeWindCachePath(options?: PatchNativeWindCacheOptions): void;
|
|
166
|
+
/**
|
|
167
|
+
* withNativeWind 的 lazy wrapper。
|
|
168
|
+
*
|
|
169
|
+
* nativewind/metro 在模块顶层捕获 react-native-css-interop 的 withCssInterop 引用,
|
|
170
|
+
* 因此必须在 patchNativeWindCachePath() 执行完之后才能加载 nativewind/metro。
|
|
171
|
+
* 这里使用 lazy require(在函数调用时才加载),确保 nativewind 拿到已 patch 的
|
|
172
|
+
* outputDirectory,而不需要在 metro.config.js 中手动处理加载顺序。
|
|
173
|
+
*
|
|
174
|
+
* 用法(metro.config.js):
|
|
175
|
+
* const { withNativeWind } = require('miaoda-expo-devkit/metro');
|
|
176
|
+
* // 不再需要 require('nativewind/metro')
|
|
177
|
+
*/
|
|
178
|
+
declare function withNativeWind(config: MetroConfig, options?: any): MetroConfig;
|
|
117
179
|
|
|
118
|
-
export { type InjectOptions, type RouteEndpointOptions, withDevStubs, withEntryInjection, withRouteEndpoint, withWorkspaceNodeModules };
|
|
180
|
+
export { type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withEntryInjection, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
package/dist/metro.d.ts
CHANGED
|
@@ -84,8 +84,8 @@ interface RouteEndpointOptions {
|
|
|
84
84
|
* 端点返回格式:
|
|
85
85
|
* {
|
|
86
86
|
* "routes": [
|
|
87
|
-
* { "title": "首页", "pageId": "/", "pageName": "index", "visible": true, "path": "index.tsx" },
|
|
88
|
-
* { "title": "id", "pageId": "/user/[id]", "pageName": "user-[id]", "visible": false, "path": "user/[id].tsx" }
|
|
87
|
+
* { "title": "首页", "pageId": "/", "pageName": "index", "visible": true, "path": "src/app/index.tsx" },
|
|
88
|
+
* { "title": "id", "pageId": "/user/[id]", "pageName": "user-[id]", "visible": false, "path": "src/app/user/[id].tsx" }
|
|
89
89
|
* ]
|
|
90
90
|
* }
|
|
91
91
|
*
|
|
@@ -114,5 +114,67 @@ declare function withRouteEndpoint(config: MetroConfig, options: RouteEndpointOp
|
|
|
114
114
|
* @returns 修正 watchFolders 和 nodeModulesPaths 后的新 Metro config
|
|
115
115
|
*/
|
|
116
116
|
declare function withWorkspaceNodeModules(config: MetroConfig): MetroConfig;
|
|
117
|
+
/**
|
|
118
|
+
* 为第三方 Expo 组件注入 NativeWind cssInterop,使其支持 className prop。
|
|
119
|
+
*
|
|
120
|
+
* 注入方式:Metro resolver 层拦截对应包的 import,重定向到 stub 文件。
|
|
121
|
+
* stub 文件透传原包的所有导出,并调用 cssInterop 完成注册。
|
|
122
|
+
* stub 内部 import 原包时,originModulePath 检查避免循环依赖。
|
|
123
|
+
*
|
|
124
|
+
* 当前支持的包:
|
|
125
|
+
* - expo-image:Image 组件(className → style)
|
|
126
|
+
* - expo-camera:CameraView 组件(className → style)
|
|
127
|
+
*
|
|
128
|
+
* 此函数在开发和生产环境均需启用(cssInterop 注册与环境无关)。
|
|
129
|
+
* 支持与其他 Metro wrapper 链式组合。
|
|
130
|
+
*
|
|
131
|
+
* @param config Metro config 对象
|
|
132
|
+
* @returns 注入 resolveRequest 后的新 Metro config
|
|
133
|
+
*/
|
|
134
|
+
declare function withCssInterop(config: MetroConfig): MetroConfig;
|
|
135
|
+
/** patchNativeWindCachePath 的配置选项 */
|
|
136
|
+
interface PatchNativeWindCacheOptions {
|
|
137
|
+
/**
|
|
138
|
+
* 缓存目录,相对于 process.cwd()(Metro 项目根目录),默认 .native-wind-cache。
|
|
139
|
+
* 与 package.json 同级,避免写入只读的 node_modules。
|
|
140
|
+
*/
|
|
141
|
+
cacheDir?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 将 NativeWind 编译缓存从 node_modules/.cache 重定向到项目根目录下的自定义目录。
|
|
145
|
+
*
|
|
146
|
+
* ⚠️ 必须在 require('nativewind/metro') / require('react-native-css-interop/dist/metro')
|
|
147
|
+
* 之前调用,否则 outputDirectory 已经固化,patch 无效。
|
|
148
|
+
*
|
|
149
|
+
* 原理:react-native-css-interop/dist/metro 在模块顶层用
|
|
150
|
+
* const outputDirectory = path.resolve(__dirname, "../../.cache")
|
|
151
|
+
* 计算缓存路径。通过在 require 前临时替换 path.resolve,可将该值重定向到
|
|
152
|
+
* 项目根目录下的自定义缓存目录,而无需 fork 或修改 node_modules 源码。
|
|
153
|
+
*
|
|
154
|
+
* 所有依赖 outputDirectory 的 NativeWind 行为(文件写入、虚拟模块 Map key、
|
|
155
|
+
* Metro resolver 返回值、haste 事件)都会一致使用新路径,无需额外 Metro 层面的 patch。
|
|
156
|
+
*
|
|
157
|
+
* ⚠️ 已知限制:依赖 react-native-css-interop 使用 path.resolve 计算缓存路径,
|
|
158
|
+
* 升级该依赖时需确认此假设仍然成立。
|
|
159
|
+
*
|
|
160
|
+
* 用法(metro.config.js):
|
|
161
|
+
* const { patchNativeWindCachePath } = require('miaoda-expo-devkit/metro');
|
|
162
|
+
* patchNativeWindCachePath(); // 必须在 require nativewind/metro 之前调用
|
|
163
|
+
* const { withNativeWind } = require('nativewind/metro');
|
|
164
|
+
*/
|
|
165
|
+
declare function patchNativeWindCachePath(options?: PatchNativeWindCacheOptions): void;
|
|
166
|
+
/**
|
|
167
|
+
* withNativeWind 的 lazy wrapper。
|
|
168
|
+
*
|
|
169
|
+
* nativewind/metro 在模块顶层捕获 react-native-css-interop 的 withCssInterop 引用,
|
|
170
|
+
* 因此必须在 patchNativeWindCachePath() 执行完之后才能加载 nativewind/metro。
|
|
171
|
+
* 这里使用 lazy require(在函数调用时才加载),确保 nativewind 拿到已 patch 的
|
|
172
|
+
* outputDirectory,而不需要在 metro.config.js 中手动处理加载顺序。
|
|
173
|
+
*
|
|
174
|
+
* 用法(metro.config.js):
|
|
175
|
+
* const { withNativeWind } = require('miaoda-expo-devkit/metro');
|
|
176
|
+
* // 不再需要 require('nativewind/metro')
|
|
177
|
+
*/
|
|
178
|
+
declare function withNativeWind(config: MetroConfig, options?: any): MetroConfig;
|
|
117
179
|
|
|
118
|
-
export { type InjectOptions, type RouteEndpointOptions, withDevStubs, withEntryInjection, withRouteEndpoint, withWorkspaceNodeModules };
|
|
180
|
+
export { type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withEntryInjection, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
package/dist/metro.js
CHANGED
|
@@ -30,8 +30,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/metro.ts
|
|
31
31
|
var metro_exports = {};
|
|
32
32
|
__export(metro_exports, {
|
|
33
|
+
patchNativeWindCachePath: () => patchNativeWindCachePath,
|
|
34
|
+
withCssInterop: () => withCssInterop,
|
|
33
35
|
withDevStubs: () => withDevStubs,
|
|
34
36
|
withEntryInjection: () => withEntryInjection,
|
|
37
|
+
withNativeWind: () => withNativeWind,
|
|
35
38
|
withRouteEndpoint: () => withRouteEndpoint,
|
|
36
39
|
withWorkspaceNodeModules: () => withWorkspaceNodeModules
|
|
37
40
|
});
|
|
@@ -44,7 +47,7 @@ var import_path2 = __toESM(require("path"));
|
|
|
44
47
|
var import_fs = __toESM(require("fs"));
|
|
45
48
|
var import_path = __toESM(require("path"));
|
|
46
49
|
var ROUTE_EXT = /\.(tsx|ts|jsx|js)$/;
|
|
47
|
-
function getRoutes(appDir) {
|
|
50
|
+
function getRoutes(appDir, rootDir) {
|
|
48
51
|
const routes = [];
|
|
49
52
|
function scan(dir, prefix) {
|
|
50
53
|
let entries;
|
|
@@ -70,17 +73,17 @@ function getRoutes(appDir) {
|
|
|
70
73
|
pageId: routePath,
|
|
71
74
|
pageName: routePath,
|
|
72
75
|
visible: !isDynamic,
|
|
73
|
-
path: import_path.default.relative(
|
|
76
|
+
path: import_path.default.relative(rootDir, fullPath)
|
|
74
77
|
});
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
scan(appDir, "");
|
|
79
|
-
return { routes };
|
|
82
|
+
return { data: routes };
|
|
80
83
|
}
|
|
81
|
-
function createRouteHandler(appDir) {
|
|
84
|
+
function createRouteHandler(appDir, rootDir) {
|
|
82
85
|
return (_req, res) => {
|
|
83
|
-
const routeTree = getRoutes(appDir);
|
|
86
|
+
const routeTree = getRoutes(appDir, rootDir);
|
|
84
87
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
85
88
|
res.end(JSON.stringify(routeTree, null, 2));
|
|
86
89
|
};
|
|
@@ -139,10 +142,11 @@ function withEntryInjection(config, options) {
|
|
|
139
142
|
var DEFAULT_ROUTE_ENDPOINT = "/__routes";
|
|
140
143
|
function withRouteEndpoint(config, options) {
|
|
141
144
|
const { appDir, endpoint = DEFAULT_ROUTE_ENDPOINT } = options;
|
|
145
|
+
const rootDir = config.projectRoot ?? process.cwd();
|
|
142
146
|
const upstream = config.server?.enhanceMiddleware ?? null;
|
|
143
147
|
const enhanceMiddleware = (middleware, metroServer) => {
|
|
144
148
|
const enhanced = upstream ? upstream(middleware, metroServer) : middleware;
|
|
145
|
-
return (0, import_connect.default)().use(enhanced).use(endpoint, createRouteHandler(appDir));
|
|
149
|
+
return (0, import_connect.default)().use(enhanced).use(endpoint, createRouteHandler(appDir, rootDir));
|
|
146
150
|
};
|
|
147
151
|
return {
|
|
148
152
|
...config,
|
|
@@ -183,10 +187,77 @@ function withWorkspaceNodeModules(config) {
|
|
|
183
187
|
}
|
|
184
188
|
};
|
|
185
189
|
}
|
|
190
|
+
var EXPO_IMAGE_STUB_FILENAME = "expo-image-stub.js";
|
|
191
|
+
var EXPO_IMAGE_STUB_PATH = import_path2.default.resolve(__dirname, "stubs", EXPO_IMAGE_STUB_FILENAME);
|
|
192
|
+
var EXPO_CAMERA_STUB_FILENAME = "expo-camera-stub.js";
|
|
193
|
+
var EXPO_CAMERA_STUB_PATH = import_path2.default.resolve(__dirname, "stubs", EXPO_CAMERA_STUB_FILENAME);
|
|
194
|
+
function withCssInterop(config) {
|
|
195
|
+
const upstream = config.resolver?.resolveRequest ?? null;
|
|
196
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
197
|
+
if (moduleName === "expo-image" && !context.originModulePath.includes(EXPO_IMAGE_STUB_FILENAME)) {
|
|
198
|
+
return { filePath: EXPO_IMAGE_STUB_PATH, type: "sourceFile" };
|
|
199
|
+
}
|
|
200
|
+
if (moduleName === "expo-camera" && !context.originModulePath.includes(EXPO_CAMERA_STUB_FILENAME)) {
|
|
201
|
+
return { filePath: EXPO_CAMERA_STUB_PATH, type: "sourceFile" };
|
|
202
|
+
}
|
|
203
|
+
if (upstream) {
|
|
204
|
+
return upstream(context, moduleName, platform);
|
|
205
|
+
}
|
|
206
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
...config,
|
|
210
|
+
resolver: {
|
|
211
|
+
...config.resolver,
|
|
212
|
+
resolveRequest
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function patchNativeWindCachePath(options = {}) {
|
|
217
|
+
const projectRoot = process.cwd();
|
|
218
|
+
const ourCacheDir = import_path2.default.resolve(projectRoot, options.cacheDir ?? ".native-wind-cache");
|
|
219
|
+
const nativewindPkgDir = import_path2.default.dirname(require.resolve("react-native-css-interop/package.json"));
|
|
220
|
+
const nativewindCacheDir = import_path2.default.resolve(nativewindPkgDir, ".cache");
|
|
221
|
+
const moduleId = require.resolve("react-native-css-interop/dist/metro");
|
|
222
|
+
delete require.cache[moduleId];
|
|
223
|
+
let intercepted = false;
|
|
224
|
+
const originalResolve = import_path2.default.resolve;
|
|
225
|
+
import_path2.default.resolve = ((...args) => {
|
|
226
|
+
const result = originalResolve(...args);
|
|
227
|
+
if (result === nativewindCacheDir) {
|
|
228
|
+
intercepted = true;
|
|
229
|
+
return ourCacheDir;
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
});
|
|
233
|
+
try {
|
|
234
|
+
require(moduleId);
|
|
235
|
+
} finally {
|
|
236
|
+
import_path2.default.resolve = originalResolve;
|
|
237
|
+
}
|
|
238
|
+
if (!intercepted) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
"patchNativeWindCachePath: patch \u672A\u751F\u6548\u2014\u2014react-native-css-interop \u53EF\u80FD\u5DF2\u4E0D\u518D\u901A\u8FC7 path.resolve \u8BA1\u7B97\u7F13\u5B58\u8DEF\u5F84\u3002\u5347\u7EA7\u8BE5\u4F9D\u8D56\u540E\u8BF7\u68C0\u67E5 dist/metro/index.js \u4E2D\u7684\u5B9E\u73B0\u3002"
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
require.resolve("react-native-css-interop/package.json");
|
|
246
|
+
patchNativeWindCachePath();
|
|
247
|
+
} catch (e) {
|
|
248
|
+
if (e.code !== "MODULE_NOT_FOUND") throw e;
|
|
249
|
+
}
|
|
250
|
+
function withNativeWind(config, options) {
|
|
251
|
+
const { withNativeWind: nwWithNativeWind } = require("nativewind/metro");
|
|
252
|
+
return nwWithNativeWind(config, options);
|
|
253
|
+
}
|
|
186
254
|
// Annotate the CommonJS export names for ESM import in node:
|
|
187
255
|
0 && (module.exports = {
|
|
256
|
+
patchNativeWindCachePath,
|
|
257
|
+
withCssInterop,
|
|
188
258
|
withDevStubs,
|
|
189
259
|
withEntryInjection,
|
|
260
|
+
withNativeWind,
|
|
190
261
|
withRouteEndpoint,
|
|
191
262
|
withWorkspaceNodeModules
|
|
192
263
|
});
|
package/dist/metro.mjs
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
1
8
|
// src/metro.ts
|
|
2
9
|
import connect from "connect";
|
|
3
10
|
import fs2 from "fs";
|
|
@@ -7,7 +14,7 @@ import path2 from "path";
|
|
|
7
14
|
import fs from "fs";
|
|
8
15
|
import path from "path";
|
|
9
16
|
var ROUTE_EXT = /\.(tsx|ts|jsx|js)$/;
|
|
10
|
-
function getRoutes(appDir) {
|
|
17
|
+
function getRoutes(appDir, rootDir) {
|
|
11
18
|
const routes = [];
|
|
12
19
|
function scan(dir, prefix) {
|
|
13
20
|
let entries;
|
|
@@ -33,17 +40,17 @@ function getRoutes(appDir) {
|
|
|
33
40
|
pageId: routePath,
|
|
34
41
|
pageName: routePath,
|
|
35
42
|
visible: !isDynamic,
|
|
36
|
-
path: path.relative(
|
|
43
|
+
path: path.relative(rootDir, fullPath)
|
|
37
44
|
});
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
47
|
}
|
|
41
48
|
scan(appDir, "");
|
|
42
|
-
return { routes };
|
|
49
|
+
return { data: routes };
|
|
43
50
|
}
|
|
44
|
-
function createRouteHandler(appDir) {
|
|
51
|
+
function createRouteHandler(appDir, rootDir) {
|
|
45
52
|
return (_req, res) => {
|
|
46
|
-
const routeTree = getRoutes(appDir);
|
|
53
|
+
const routeTree = getRoutes(appDir, rootDir);
|
|
47
54
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
48
55
|
res.end(JSON.stringify(routeTree, null, 2));
|
|
49
56
|
};
|
|
@@ -102,10 +109,11 @@ function withEntryInjection(config, options) {
|
|
|
102
109
|
var DEFAULT_ROUTE_ENDPOINT = "/__routes";
|
|
103
110
|
function withRouteEndpoint(config, options) {
|
|
104
111
|
const { appDir, endpoint = DEFAULT_ROUTE_ENDPOINT } = options;
|
|
112
|
+
const rootDir = config.projectRoot ?? process.cwd();
|
|
105
113
|
const upstream = config.server?.enhanceMiddleware ?? null;
|
|
106
114
|
const enhanceMiddleware = (middleware, metroServer) => {
|
|
107
115
|
const enhanced = upstream ? upstream(middleware, metroServer) : middleware;
|
|
108
|
-
return connect().use(enhanced).use(endpoint, createRouteHandler(appDir));
|
|
116
|
+
return connect().use(enhanced).use(endpoint, createRouteHandler(appDir, rootDir));
|
|
109
117
|
};
|
|
110
118
|
return {
|
|
111
119
|
...config,
|
|
@@ -146,9 +154,76 @@ function withWorkspaceNodeModules(config) {
|
|
|
146
154
|
}
|
|
147
155
|
};
|
|
148
156
|
}
|
|
157
|
+
var EXPO_IMAGE_STUB_FILENAME = "expo-image-stub.js";
|
|
158
|
+
var EXPO_IMAGE_STUB_PATH = path2.resolve(__dirname, "stubs", EXPO_IMAGE_STUB_FILENAME);
|
|
159
|
+
var EXPO_CAMERA_STUB_FILENAME = "expo-camera-stub.js";
|
|
160
|
+
var EXPO_CAMERA_STUB_PATH = path2.resolve(__dirname, "stubs", EXPO_CAMERA_STUB_FILENAME);
|
|
161
|
+
function withCssInterop(config) {
|
|
162
|
+
const upstream = config.resolver?.resolveRequest ?? null;
|
|
163
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
164
|
+
if (moduleName === "expo-image" && !context.originModulePath.includes(EXPO_IMAGE_STUB_FILENAME)) {
|
|
165
|
+
return { filePath: EXPO_IMAGE_STUB_PATH, type: "sourceFile" };
|
|
166
|
+
}
|
|
167
|
+
if (moduleName === "expo-camera" && !context.originModulePath.includes(EXPO_CAMERA_STUB_FILENAME)) {
|
|
168
|
+
return { filePath: EXPO_CAMERA_STUB_PATH, type: "sourceFile" };
|
|
169
|
+
}
|
|
170
|
+
if (upstream) {
|
|
171
|
+
return upstream(context, moduleName, platform);
|
|
172
|
+
}
|
|
173
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
...config,
|
|
177
|
+
resolver: {
|
|
178
|
+
...config.resolver,
|
|
179
|
+
resolveRequest
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function patchNativeWindCachePath(options = {}) {
|
|
184
|
+
const projectRoot = process.cwd();
|
|
185
|
+
const ourCacheDir = path2.resolve(projectRoot, options.cacheDir ?? ".native-wind-cache");
|
|
186
|
+
const nativewindPkgDir = path2.dirname(__require.resolve("react-native-css-interop/package.json"));
|
|
187
|
+
const nativewindCacheDir = path2.resolve(nativewindPkgDir, ".cache");
|
|
188
|
+
const moduleId = __require.resolve("react-native-css-interop/dist/metro");
|
|
189
|
+
delete __require.cache[moduleId];
|
|
190
|
+
let intercepted = false;
|
|
191
|
+
const originalResolve = path2.resolve;
|
|
192
|
+
path2.resolve = ((...args) => {
|
|
193
|
+
const result = originalResolve(...args);
|
|
194
|
+
if (result === nativewindCacheDir) {
|
|
195
|
+
intercepted = true;
|
|
196
|
+
return ourCacheDir;
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
});
|
|
200
|
+
try {
|
|
201
|
+
__require(moduleId);
|
|
202
|
+
} finally {
|
|
203
|
+
path2.resolve = originalResolve;
|
|
204
|
+
}
|
|
205
|
+
if (!intercepted) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"patchNativeWindCachePath: patch \u672A\u751F\u6548\u2014\u2014react-native-css-interop \u53EF\u80FD\u5DF2\u4E0D\u518D\u901A\u8FC7 path.resolve \u8BA1\u7B97\u7F13\u5B58\u8DEF\u5F84\u3002\u5347\u7EA7\u8BE5\u4F9D\u8D56\u540E\u8BF7\u68C0\u67E5 dist/metro/index.js \u4E2D\u7684\u5B9E\u73B0\u3002"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
__require.resolve("react-native-css-interop/package.json");
|
|
213
|
+
patchNativeWindCachePath();
|
|
214
|
+
} catch (e) {
|
|
215
|
+
if (e.code !== "MODULE_NOT_FOUND") throw e;
|
|
216
|
+
}
|
|
217
|
+
function withNativeWind(config, options) {
|
|
218
|
+
const { withNativeWind: nwWithNativeWind } = __require("nativewind/metro");
|
|
219
|
+
return nwWithNativeWind(config, options);
|
|
220
|
+
}
|
|
149
221
|
export {
|
|
222
|
+
patchNativeWindCachePath,
|
|
223
|
+
withCssInterop,
|
|
150
224
|
withDevStubs,
|
|
151
225
|
withEntryInjection,
|
|
226
|
+
withNativeWind,
|
|
152
227
|
withRouteEndpoint,
|
|
153
228
|
withWorkspaceNodeModules
|
|
154
229
|
};
|
|
@@ -118,7 +118,7 @@ var noDuplicateExpoRouterUrlRule = {
|
|
|
118
118
|
const others = files.filter((f) => f !== rel);
|
|
119
119
|
if (others.length === 0) return;
|
|
120
120
|
context.report({
|
|
121
|
-
|
|
121
|
+
loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
122
122
|
messageId: "duplicateUrl",
|
|
123
123
|
data: { url, other: others.join(", ") }
|
|
124
124
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var ExpoCamera = __toESM(require("expo-camera"));
|
|
25
|
+
var import_nativewind = require("nativewind");
|
|
26
|
+
(0, import_nativewind.cssInterop)(ExpoCamera.CameraView, { className: "style" });
|
|
27
|
+
module.exports = ExpoCamera;
|
|
28
|
+
//# sourceMappingURL=expo-camera-stub.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
+
mod
|
|
23
|
+
));
|
|
24
|
+
var ExpoImage = __toESM(require("expo-image"));
|
|
25
|
+
var import_nativewind = require("nativewind");
|
|
26
|
+
(0, import_nativewind.cssInterop)(ExpoImage.Image, { className: "style" });
|
|
27
|
+
module.exports = ExpoImage;
|
|
28
|
+
//# sourceMappingURL=expo-image-stub.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": ["typescript", "react"],
|
|
3
|
+
"jsPlugins": [
|
|
4
|
+
"eslint-plugin-expo",
|
|
5
|
+
{ "name": "expo-config-plugin", "specifier": "miaoda-expo-devkit/rules/no-undeclared-expo-plugin" },
|
|
6
|
+
{ "name": "expo-router", "specifier": "miaoda-expo-devkit/rules/no-unstable-expo-router" },
|
|
7
|
+
{ "name": "rn-web", "specifier": "miaoda-expo-devkit/rules/no-rn-alert" },
|
|
8
|
+
{ "name": "expo-router-url", "specifier": "miaoda-expo-devkit/rules/no-duplicate-expo-router-url" }
|
|
9
|
+
],
|
|
10
|
+
|
|
11
|
+
"categories": {
|
|
12
|
+
"correctness": "allow"
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
"rules": {
|
|
16
|
+
"expo-config-plugin/no-undeclared-expo-plugin": "error",
|
|
17
|
+
"expo-router/no-unstable-expo-router": "error",
|
|
18
|
+
"rn-web/no-rn-alert": "error",
|
|
19
|
+
"expo-router-url/no-duplicate-expo-router-url": "error",
|
|
20
|
+
|
|
21
|
+
"expo/no-dynamic-env-var": "error",
|
|
22
|
+
"expo/no-env-var-destructuring": "error",
|
|
23
|
+
"expo/use-dom-exports": "error",
|
|
24
|
+
|
|
25
|
+
"no-undef": "error",
|
|
26
|
+
"use-isnan": "error",
|
|
27
|
+
"valid-typeof": "error",
|
|
28
|
+
"no-dupe-keys": "error",
|
|
29
|
+
"no-duplicate-case": "error",
|
|
30
|
+
"no-dupe-class-members": "error",
|
|
31
|
+
|
|
32
|
+
"react/react-in-jsx-scope": "off",
|
|
33
|
+
"react/jsx-no-duplicate-props": "error",
|
|
34
|
+
"react/jsx-no-undef": "error"
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
"env": {
|
|
38
|
+
"browser": true
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
"globals": {
|
|
42
|
+
"__DEV__": "readonly",
|
|
43
|
+
"process": "readonly",
|
|
44
|
+
"global": "readonly",
|
|
45
|
+
"localStorage": "readonly"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miaoda-expo-devkit",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.5",
|
|
4
4
|
"description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"devkit-lint": "./dist/cli/lint.js"
|
|
11
|
+
},
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
11
14
|
"import": {
|
|
@@ -41,11 +44,13 @@
|
|
|
41
44
|
"./rules/no-unstable-expo-router": "./dist/rules/no-unstable-expo-router.js",
|
|
42
45
|
"./rules/no-rn-alert": "./dist/rules/no-rn-alert.js",
|
|
43
46
|
"./rules/no-duplicate-expo-router-url": "./dist/rules/no-duplicate-expo-router-url.js",
|
|
44
|
-
"./biome": "./biome-config.json"
|
|
47
|
+
"./biome": "./biome-config.json",
|
|
48
|
+
"./oxlint": "./oxlint-config.json"
|
|
45
49
|
},
|
|
46
50
|
"files": [
|
|
47
51
|
"dist",
|
|
48
52
|
"biome-config.json",
|
|
53
|
+
"oxlint-config.json",
|
|
49
54
|
"pnpm-config.json",
|
|
50
55
|
"!dist/**/*.map"
|
|
51
56
|
],
|
|
@@ -82,6 +87,7 @@
|
|
|
82
87
|
"@types/babel__core": "^7.20.5",
|
|
83
88
|
"@types/connect": "^3.4.38",
|
|
84
89
|
"@types/node": "^25.0.0",
|
|
90
|
+
"metro": "^0.83.0",
|
|
85
91
|
"metro-config": "^0.83.0",
|
|
86
92
|
"metro-resolver": "^0.83.0",
|
|
87
93
|
"oxlint": "^1.60.0",
|