@rn-dev-inspector/rn-compat 0.0.0-stage → 1.3.0
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/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +32 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +106 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +43 -3
- package/shims/emptyModule.js +7 -0
- package/shims/inspectorDataShim.js +358 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vahe Saroyan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @rn-dev-inspector/rn-compat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native version compatibility layer for [React Native Dev Inspector](https://github.com/VaheSaroyan/react-native-dev-inspector).
|
|
4
|
+
|
|
5
|
+
A Metro resolver shim that keeps the inspector hierarchy working across React Native versions — the internal `getInspectorDataForViewAtPoint` module moved to a new private path on RN 0.82+, and its fiber contract differs between Paper (≤ 0.72) and Fabric (0.73+).
|
|
6
|
+
|
|
7
|
+
## Supported RN versions
|
|
8
|
+
|
|
9
|
+
| RN version | Architecture | Status | Notes |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| ≤ 0.72 | Paper | ✅ Supported | Rebuilds the hierarchy by looking the tapped host fiber up via `touchedViewTag`; wrapper components that don't forward props (e.g. `Button`) still resolve to the business JSX line |
|
|
12
|
+
| 0.73 – 0.81 | Fabric | ✅ Supported | Restores hierarchy fibers and finds source in the subtree |
|
|
13
|
+
| 0.82+ | Fabric | ✅ Supported | Intercepts the new private devsupport path; shim rebuilds the hierarchy (verified on 0.86) |
|
|
14
|
+
|
|
15
|
+
> **Compatibility caveat:** the layer intercepts react-native's *internal* module paths (`react-native/src/private/...`, `react-native/Libraries/Inspector/...`). These are not public API and can move in any release. When upgrading RN past the versions listed above, verify the inspector still works; if a path moved, the compat layer will log a warning and degrade to the stock inspector instead of breaking the bundler.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
// metro.config.js
|
|
21
|
+
const { getDefaultConfig } = require('@react-native/metro-config');
|
|
22
|
+
const { withInspector } = require('react-native-dev-inspector/metro');
|
|
23
|
+
const { withInspectorCompat } = require('@rn-dev-inspector/rn-compat');
|
|
24
|
+
|
|
25
|
+
module.exports = withInspector(withInspectorCompat(getDefaultConfig(__dirname)));
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`withInspectorCompat` never mutates the config you pass in (it returns a new object) and never throws at config load time: if the react-native implementation cannot be located it logs a warning and returns the config without the compat resolver, so the bundler keeps running.
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
|
|
32
|
+
- `root` — app root directory used to locate react-native; defaults to `config.projectRoot` or `process.cwd()`. In a monorepo the lookup walks up parent directories, so a hoisted react-native in the workspace root's `node_modules` is found automatically.
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
1. **Interception** — the resolver redirects react-native's inspector module (both the legacy `Libraries/Inspector` path and the 0.82+ `src/private` path) to a bundled shim.
|
|
37
|
+
2. **Shim** — the shim requires the *real* implementation through the marker name `__rn_dev_inspector_original_impl__`, which the resolver maps back to the actual file in the project's react-native install (resolved to an absolute path at config time, since the shim is not on the project's node_modules chain).
|
|
38
|
+
3. **Polyfills** — `@rn-dev-inspector/rn-compat/runtime` (re-exported as `react-native-dev-inspector/rn-compat`) contains runtime fixes for Paper/Fabric differences.
|
|
39
|
+
4. **Watch folders** — the shim package (and the react-native package, when it lives outside the project, e.g. in a monorepo workspace root) is appended to `config.watchFolders` so it participates in bundling.
|
|
40
|
+
5. **Optional peers** — `expo-dev-menu` / `expo-dev-client` are resolved to an empty module when not installed, so Metro doesn't generate a stub that throws "Requiring unknown module" at runtime.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rn-dev-inspector/rn-compat
|
|
3
|
+
*
|
|
4
|
+
* React Native version compatibility layer. One-line integration in
|
|
5
|
+
* metro.config.js:
|
|
6
|
+
*
|
|
7
|
+
* const { withInspector } = require('react-native-dev-inspector/metro');
|
|
8
|
+
* const { withInspectorCompat } = require('@rn-dev-inspector/rn-compat');
|
|
9
|
+
* module.exports = withInspector(withInspectorCompat(getDefaultConfig(__dirname)));
|
|
10
|
+
*
|
|
11
|
+
* Adaptation matrix:
|
|
12
|
+
* RN <= 0.72 (Paper) : fix Metro optional-dependency mis-resolution +
|
|
13
|
+
* rebuild hierarchy by looking the host fiber up
|
|
14
|
+
* via touchedViewTag (wrapper components that do
|
|
15
|
+
* not forward props, like Button, also resolve to
|
|
16
|
+
* the business JSX line)
|
|
17
|
+
* RN 0.73-0.81 (Fabric) : fix optional-dependency mis-resolution + restore
|
|
18
|
+
* hierarchy fibers + find source in subtree
|
|
19
|
+
* RN 0.82+ : intercept the new private path -> shim rebuilds
|
|
20
|
+
* the hierarchy (verified on 0.86)
|
|
21
|
+
*
|
|
22
|
+
* The shim refers back to the current RN's real getInspectorDataForViewAtPoint
|
|
23
|
+
* implementation through the marker module name
|
|
24
|
+
* `__rn_dev_inspector_original_impl__`; the marker name is resolved to an
|
|
25
|
+
* absolute path at config time, so the shim is decoupled from the project's
|
|
26
|
+
* location (the shim file is not on the project's node_modules chain — a bare
|
|
27
|
+
* module name would fail to resolve).
|
|
28
|
+
*/
|
|
29
|
+
interface WithInspectorCompatOptions {
|
|
30
|
+
/** App root directory; defaults to config.projectRoot or process.cwd() */
|
|
31
|
+
root?: string;
|
|
32
|
+
}
|
|
33
|
+
type MetroResolver = (context: any, moduleName: string, platform: string | null) => {
|
|
34
|
+
type: string;
|
|
35
|
+
filePath: string;
|
|
36
|
+
} | any;
|
|
37
|
+
interface MetroConfigLike {
|
|
38
|
+
projectRoot?: string;
|
|
39
|
+
watchFolders?: string[];
|
|
40
|
+
resolver?: {
|
|
41
|
+
resolveRequest?: MetroResolver;
|
|
42
|
+
[key: string]: any;
|
|
43
|
+
};
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Wrap the metro config:
|
|
48
|
+
* - intercept the 0.82 private path -> bundled shim (the shim requires the
|
|
49
|
+
* real implementation internally)
|
|
50
|
+
* - intercept the marker module name -> real implementation file
|
|
51
|
+
* - all other requests continue with the original resolveRequest (if any)
|
|
52
|
+
*
|
|
53
|
+
* Never throws and never mutates the input config: the returned object is a
|
|
54
|
+
* shallow copy, so it composes safely
|
|
55
|
+
* (`withInspector(withInspectorCompat(config))`). When the react-native
|
|
56
|
+
* implementation cannot be located it warns and returns the config with the
|
|
57
|
+
* compat resolver omitted — a broken integration must not take down the
|
|
58
|
+
* entire bundler.
|
|
59
|
+
*/
|
|
60
|
+
export declare function withInspectorCompat<T extends MetroConfigLike = MetroConfigLike>(config: T, options?: WithInspectorCompatOptions): T;
|
|
61
|
+
export default withInspectorCompat;
|
|
62
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAuBH,UAAU,0BAA0B;IAClC,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,KAAK,aAAa,GAAG,CACnB,OAAO,EAAE,GAAG,EACZ,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,KACpB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAAC;AAE9C,UAAU,eAAe;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE;QACT,cAAc,CAAC,EAAE,aAAa,CAAC;QAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AA+BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,EAC7E,MAAM,EAAE,CAAC,EACT,OAAO,GAAE,0BAA+B,GACvC,CAAC,CAkFH;AAED,eAAe,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @rn-dev-inspector/rn-compat
|
|
4
|
+
*
|
|
5
|
+
* React Native version compatibility layer. One-line integration in
|
|
6
|
+
* metro.config.js:
|
|
7
|
+
*
|
|
8
|
+
* const { withInspector } = require('react-native-dev-inspector/metro');
|
|
9
|
+
* const { withInspectorCompat } = require('@rn-dev-inspector/rn-compat');
|
|
10
|
+
* module.exports = withInspector(withInspectorCompat(getDefaultConfig(__dirname)));
|
|
11
|
+
*
|
|
12
|
+
* Adaptation matrix:
|
|
13
|
+
* RN <= 0.72 (Paper) : fix Metro optional-dependency mis-resolution +
|
|
14
|
+
* rebuild hierarchy by looking the host fiber up
|
|
15
|
+
* via touchedViewTag (wrapper components that do
|
|
16
|
+
* not forward props, like Button, also resolve to
|
|
17
|
+
* the business JSX line)
|
|
18
|
+
* RN 0.73-0.81 (Fabric) : fix optional-dependency mis-resolution + restore
|
|
19
|
+
* hierarchy fibers + find source in subtree
|
|
20
|
+
* RN 0.82+ : intercept the new private path -> shim rebuilds
|
|
21
|
+
* the hierarchy (verified on 0.86)
|
|
22
|
+
*
|
|
23
|
+
* The shim refers back to the current RN's real getInspectorDataForViewAtPoint
|
|
24
|
+
* implementation through the marker module name
|
|
25
|
+
* `__rn_dev_inspector_original_impl__`; the marker name is resolved to an
|
|
26
|
+
* absolute path at config time, so the shim is decoupled from the project's
|
|
27
|
+
* location (the shim file is not on the project's node_modules chain — a bare
|
|
28
|
+
* module name would fail to resolve).
|
|
29
|
+
*/
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.withInspectorCompat = withInspectorCompat;
|
|
65
|
+
const fs = __importStar(require("fs"));
|
|
66
|
+
const path = __importStar(require("path"));
|
|
67
|
+
// New private path on RN 0.82+, which Inspector.js requires first
|
|
68
|
+
const PRIVATE_082 = 'react-native/src/private/devsupport/devmenu/elementinspector/getInspectorDataForViewAtPoint';
|
|
69
|
+
// Public path on RN <= 0.80, required by core on Paper projects (e.g. 0.72).
|
|
70
|
+
// Intercepted to the shim so Paper can rebuild the hierarchy too (the shim
|
|
71
|
+
// comes back to this file via MARKER).
|
|
72
|
+
const PUBLIC_LE080 = 'react-native/Libraries/Inspector/getInspectorDataForViewAtPoint';
|
|
73
|
+
// The shim requires the real implementation through this marker name; the
|
|
74
|
+
// adapter resolves it to an absolute path at config time
|
|
75
|
+
const MARKER = '__rn_dev_inspector_original_impl__';
|
|
76
|
+
// Optional peers: resolve to an empty module when not installed so core's
|
|
77
|
+
// try/catch degrades gracefully (otherwise Metro generates a stub that throws
|
|
78
|
+
// at runtime and dev reports "Requiring unknown module")
|
|
79
|
+
const OPTIONAL_PEERS = ['expo-dev-menu', 'expo-dev-client'];
|
|
80
|
+
/**
|
|
81
|
+
* Probe the project's react-native for the real getInspectorDataForViewAtPoint
|
|
82
|
+
* implementation, by priority:
|
|
83
|
+
* 0.82+ : src/private/devsupport/devmenu/elementinspector/...
|
|
84
|
+
* <=0.80: Libraries/Inspector/...
|
|
85
|
+
*
|
|
86
|
+
* Walks up from `root` (node resolution style) so a monorepo app whose
|
|
87
|
+
* react-native is hoisted into a workspace root's node_modules is found too;
|
|
88
|
+
* in that case the impl package lives outside the project root and must be
|
|
89
|
+
* added to watchFolders separately (see withInspectorCompat).
|
|
90
|
+
*/
|
|
91
|
+
function findRealImpl(root) {
|
|
92
|
+
// Derived from the intercepted module specifiers above so the two stay in sync
|
|
93
|
+
const candidates = [PRIVATE_082, PUBLIC_LE080].map(moduleName => `node_modules/${moduleName}.js`);
|
|
94
|
+
let dir = path.resolve(root);
|
|
95
|
+
for (;;) {
|
|
96
|
+
for (const rel of candidates) {
|
|
97
|
+
const p = path.join(dir, rel);
|
|
98
|
+
if (fs.existsSync(p))
|
|
99
|
+
return p;
|
|
100
|
+
}
|
|
101
|
+
const parent = path.dirname(dir);
|
|
102
|
+
// Reached the filesystem root without a hit
|
|
103
|
+
if (parent === dir)
|
|
104
|
+
return null;
|
|
105
|
+
dir = parent;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Wrap the metro config:
|
|
110
|
+
* - intercept the 0.82 private path -> bundled shim (the shim requires the
|
|
111
|
+
* real implementation internally)
|
|
112
|
+
* - intercept the marker module name -> real implementation file
|
|
113
|
+
* - all other requests continue with the original resolveRequest (if any)
|
|
114
|
+
*
|
|
115
|
+
* Never throws and never mutates the input config: the returned object is a
|
|
116
|
+
* shallow copy, so it composes safely
|
|
117
|
+
* (`withInspector(withInspectorCompat(config))`). When the react-native
|
|
118
|
+
* implementation cannot be located it warns and returns the config with the
|
|
119
|
+
* compat resolver omitted — a broken integration must not take down the
|
|
120
|
+
* entire bundler.
|
|
121
|
+
*/
|
|
122
|
+
function withInspectorCompat(config, options = {}) {
|
|
123
|
+
// Prefer Metro's projectRoot (in a monorepo the repo root is not the app
|
|
124
|
+
// root; the real implementation must be found in the app's own node_modules)
|
|
125
|
+
const root = options.root || config.projectRoot || process.cwd();
|
|
126
|
+
const realImpl = findRealImpl(root);
|
|
127
|
+
if (!realImpl) {
|
|
128
|
+
// Degrade instead of throwing: without the real implementation the shim
|
|
129
|
+
// cannot resolve back to RN's own code, so compat interception is skipped
|
|
130
|
+
// entirely and the bundler keeps working with the stock inspector.
|
|
131
|
+
console.warn('[rn-dev-inspector] @rn-dev-inspector/rn-compat: could not find the ' +
|
|
132
|
+
'react-native getInspectorDataForViewAtPoint implementation under ' +
|
|
133
|
+
root +
|
|
134
|
+
' — skipping the compat resolver. Pass { root } if your project ' +
|
|
135
|
+
'root is not where react-native is installed.');
|
|
136
|
+
return { ...config };
|
|
137
|
+
}
|
|
138
|
+
// After compilation this file lives in dist/; the shim is in the package
|
|
139
|
+
// root's shims/ directory
|
|
140
|
+
const shimPath = path.resolve(__dirname, '..', 'shims', 'inspectorDataShim.js');
|
|
141
|
+
const emptyModulePath = path.resolve(__dirname, '..', 'shims', 'emptyModule.js');
|
|
142
|
+
// Approximate Metro's resolution with Node's rules (config time, node side):
|
|
143
|
+
// an expo package counts as "installed" only if resolvable from the project
|
|
144
|
+
// (or the monorepo root)
|
|
145
|
+
const isInstalled = (name) => {
|
|
146
|
+
try {
|
|
147
|
+
require.resolve(`${name}/package.json`, { paths: [root] });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const prev = (config.resolver && config.resolver.resolveRequest) || null;
|
|
155
|
+
// Both the shim and the real implementation file must be within Metro's
|
|
156
|
+
// watch scope to participate in bundling (in a monorepo the real
|
|
157
|
+
// implementation may sit in the workspace root's node_modules).
|
|
158
|
+
// Not needed when the react-native package is inside the project (Metro
|
|
159
|
+
// 0.76's TreeFS throws "Could not add directory" because watchFolders
|
|
160
|
+
// duplicates a directory already under the project root), and adding it is
|
|
161
|
+
// actively harmful; only add it when the package lives outside the project
|
|
162
|
+
// (monorepo).
|
|
163
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
164
|
+
const implPkgRoot = realImpl.match(/^.*node_modules[\\/]react-native/);
|
|
165
|
+
const implOutsideProject = implPkgRoot &&
|
|
166
|
+
(() => {
|
|
167
|
+
const rel = path.relative(root, implPkgRoot[0]);
|
|
168
|
+
return !!rel && (rel.startsWith('..') || path.isAbsolute(rel));
|
|
169
|
+
})();
|
|
170
|
+
const extraWatch = [
|
|
171
|
+
pkgRoot,
|
|
172
|
+
...(implOutsideProject ? [implPkgRoot[0]] : []),
|
|
173
|
+
];
|
|
174
|
+
// Build a new config object instead of mutating the input, so wrapping is
|
|
175
|
+
// idempotent-safe and composes with other with* helpers
|
|
176
|
+
const nextConfig = {
|
|
177
|
+
...config,
|
|
178
|
+
watchFolders: [
|
|
179
|
+
...new Set([...(config.watchFolders || []), ...extraWatch]),
|
|
180
|
+
],
|
|
181
|
+
resolver: {
|
|
182
|
+
...(config.resolver || {}),
|
|
183
|
+
resolveRequest(context, moduleName, platform) {
|
|
184
|
+
if (moduleName === PRIVATE_082 || moduleName === PUBLIC_LE080) {
|
|
185
|
+
return { type: 'sourceFile', filePath: shimPath };
|
|
186
|
+
}
|
|
187
|
+
if (moduleName === MARKER) {
|
|
188
|
+
return { type: 'sourceFile', filePath: realImpl };
|
|
189
|
+
}
|
|
190
|
+
if (OPTIONAL_PEERS.includes(moduleName) && !isInstalled(moduleName)) {
|
|
191
|
+
return { type: 'sourceFile', filePath: emptyModulePath };
|
|
192
|
+
}
|
|
193
|
+
if (prev)
|
|
194
|
+
return prev(context, moduleName, platform);
|
|
195
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
return nextConfig;
|
|
200
|
+
}
|
|
201
|
+
exports.default = withInspectorCompat;
|
|
202
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rn-dev-inspector/rn-compat — runtime patch entry
|
|
3
|
+
*
|
|
4
|
+
* Decoupled from withInspectorCompat (the build-time resolver adaptation in
|
|
5
|
+
* metro.config.js). This file collects side-effect polyfills for "runtime API
|
|
6
|
+
* gaps on older RN".
|
|
7
|
+
* Usage (already imported by default by the umbrella package
|
|
8
|
+
* react-native-dev-inspector, no manual import needed):
|
|
9
|
+
*
|
|
10
|
+
* import '@rn-dev-inspector/rn-compat/dist/runtime';
|
|
11
|
+
*
|
|
12
|
+
* Constraint: the build output of this file gets bundled into any RN project,
|
|
13
|
+
* so it must stay ES5-compatible (plain for loops, no destructuring/spread/
|
|
14
|
+
* async) to avoid tsc downleveling to @babel/runtime helpers — rn-compat is a
|
|
15
|
+
* zero-runtime-dependency config package that may not resolve in every
|
|
16
|
+
* project. Each patch must feature-detect and only fill in what is missing;
|
|
17
|
+
* it is a no-op on Node/newer RN.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* React Native defines this in every bundle; it is absent under plain Node
|
|
21
|
+
* (tests), where we treat the environment as development.
|
|
22
|
+
*/
|
|
23
|
+
declare const __DEV__: boolean | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* URLSearchParams polyfill for RN <= 0.72 (react-native/Libraries/Blob/URL.js):
|
|
26
|
+
* only get/getAll/has/append exist; set() throws outright with
|
|
27
|
+
* "URLSearchParams.set is not implemented". The inspector's Open-in-editor
|
|
28
|
+
* needs set() to build the query string, so the missing write methods are
|
|
29
|
+
* filled in here with WHATWG semantics.
|
|
30
|
+
*/
|
|
31
|
+
declare function patchURLSearchParams(): void;
|
|
32
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;GAGG;AACH,OAAO,CAAC,MAAM,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;AAE3C;;;;;;GAMG;AACH,iBAAS,oBAAoB,IAAI,IAAI,CAkEpC"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @rn-dev-inspector/rn-compat — runtime patch entry
|
|
4
|
+
*
|
|
5
|
+
* Decoupled from withInspectorCompat (the build-time resolver adaptation in
|
|
6
|
+
* metro.config.js). This file collects side-effect polyfills for "runtime API
|
|
7
|
+
* gaps on older RN".
|
|
8
|
+
* Usage (already imported by default by the umbrella package
|
|
9
|
+
* react-native-dev-inspector, no manual import needed):
|
|
10
|
+
*
|
|
11
|
+
* import '@rn-dev-inspector/rn-compat/dist/runtime';
|
|
12
|
+
*
|
|
13
|
+
* Constraint: the build output of this file gets bundled into any RN project,
|
|
14
|
+
* so it must stay ES5-compatible (plain for loops, no destructuring/spread/
|
|
15
|
+
* async) to avoid tsc downleveling to @babel/runtime helpers — rn-compat is a
|
|
16
|
+
* zero-runtime-dependency config package that may not resolve in every
|
|
17
|
+
* project. Each patch must feature-detect and only fill in what is missing;
|
|
18
|
+
* it is a no-op on Node/newer RN.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* URLSearchParams polyfill for RN <= 0.72 (react-native/Libraries/Blob/URL.js):
|
|
22
|
+
* only get/getAll/has/append exist; set() throws outright with
|
|
23
|
+
* "URLSearchParams.set is not implemented". The inspector's Open-in-editor
|
|
24
|
+
* needs set() to build the query string, so the missing write methods are
|
|
25
|
+
* filled in here with WHATWG semantics.
|
|
26
|
+
*/
|
|
27
|
+
function patchURLSearchParams() {
|
|
28
|
+
const ParamsAny = globalThis.URLSearchParams;
|
|
29
|
+
if (typeof ParamsAny !== 'function')
|
|
30
|
+
return;
|
|
31
|
+
const proto = ParamsAny.prototype;
|
|
32
|
+
// Behavioral probe: RN 0.72's polyfill "has" set() on its prototype, but
|
|
33
|
+
// calling it throws "URLSearchParams.set is not implemented" — a typeof
|
|
34
|
+
// check alone would be a false positive.
|
|
35
|
+
let setWorks = false;
|
|
36
|
+
try {
|
|
37
|
+
const probe = new ParamsAny('a=1');
|
|
38
|
+
probe.set('a', '2');
|
|
39
|
+
setWorks = probe.toString() === 'a=2';
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
setWorks = false;
|
|
43
|
+
}
|
|
44
|
+
// set: replace the first same-name entry (keeping order), or append at the end
|
|
45
|
+
if (!setWorks) {
|
|
46
|
+
proto.set = function set(name, value) {
|
|
47
|
+
name = String(name);
|
|
48
|
+
value = String(value);
|
|
49
|
+
const hasGet = typeof this.get === 'function';
|
|
50
|
+
if (hasGet && this.get(name) === null) {
|
|
51
|
+
this.append(name, value);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const internal = this._searchParams;
|
|
55
|
+
if (Array.isArray(internal)) {
|
|
56
|
+
let replaced = false;
|
|
57
|
+
const kept = [];
|
|
58
|
+
for (let i = 0; i < internal.length; i++) {
|
|
59
|
+
const pair = internal[i];
|
|
60
|
+
if (pair && pair.name === name) {
|
|
61
|
+
if (!replaced) {
|
|
62
|
+
pair.value = value;
|
|
63
|
+
replaced = true;
|
|
64
|
+
kept.push(pair);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
kept.push(pair);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
this._searchParams = kept;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Unknown internal structure: cannot replace precisely when get()
|
|
75
|
+
// returns a value, degrade to append (get() returns the first
|
|
76
|
+
// same-name entry, so the result is still correct)
|
|
77
|
+
this.append(name, value);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (typeof proto.delete !== 'function') {
|
|
82
|
+
proto.delete = function deleteParam(name) {
|
|
83
|
+
const internal = this._searchParams;
|
|
84
|
+
if (Array.isArray(internal)) {
|
|
85
|
+
const kept = [];
|
|
86
|
+
for (let i = 0; i < internal.length; i++) {
|
|
87
|
+
const pair = internal[i];
|
|
88
|
+
if (!(pair && pair.name === String(name)))
|
|
89
|
+
kept.push(pair);
|
|
90
|
+
}
|
|
91
|
+
this._searchParams = kept;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Dev-only. This mutates a global prototype, so it must never run in a
|
|
97
|
+
// release build: a developer tool has no business changing the runtime
|
|
98
|
+
// semantics of the shipped app. The inspector's own use of URLSearchParams
|
|
99
|
+
// is already safe without this patch - buildEditorQueryString (see
|
|
100
|
+
// packages/core/src/editorQuery.ts) probes behaviourally and falls back to
|
|
101
|
+
// manual percent-encoding - so the patch is a convenience for app code on
|
|
102
|
+
// old RN, not something the inspector depends on.
|
|
103
|
+
if (typeof __DEV__ === 'undefined' || __DEV__) {
|
|
104
|
+
patchURLSearchParams();
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rn-dev-inspector/rn-compat",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "React Native version compatibility layer for React Native Dev Inspector - Metro resolver shim that rebuilds the inspector hierarchy on Fabric (RN 0.73+/0.86+)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"shims"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"inspector",
|
|
14
|
+
"devtools",
|
|
15
|
+
"metro",
|
|
16
|
+
"fabric",
|
|
17
|
+
"compat"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/VaheSaroyan/react-native-dev-inspector.git",
|
|
23
|
+
"directory": "packages/rn-compat"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/VaheSaroyan/react-native-dev-inspector/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/VaheSaroyan/react-native-dev-inspector#readme",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.10.0"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./dist/index.js",
|
|
34
|
+
"./runtime": "./dist/runtime.js",
|
|
35
|
+
"./runtime.js": "./dist/runtime.js",
|
|
36
|
+
"./dist/runtime": "./dist/runtime.js",
|
|
37
|
+
"./dist/runtime.js": "./dist/runtime.js",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc",
|
|
42
|
+
"clean": "rm -rf dist",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"test": "pnpm build && node --test \"test/*.test.js\""
|
|
45
|
+
}
|
|
6
46
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Empty module placeholder. The rn-compat resolver maps optional peer
|
|
3
|
+
* dependencies (expo-dev-menu / expo-dev-client) here when they are not
|
|
4
|
+
* installed, so core's `try { require('expo-dev-menu') } catch {}`
|
|
5
|
+
* degrades to a no-op instead of Metro's runtime-throwing stub.
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {};
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptation shim for getInspectorDataForViewAtPoint (works on both Fabric and Paper).
|
|
3
|
+
*
|
|
4
|
+
* Adaptation point 2: the inspector hierarchy on RN 0.8x/0.86 Fabric has two problems:
|
|
5
|
+
* 1. Hierarchy items carry only {props, measure} and no fiber, and _debugSource was
|
|
6
|
+
* removed in React 19, so the library has to rely on the babel-injected
|
|
7
|
+
* __callerSource — without the fiber there is no source info at all;
|
|
8
|
+
* 2. The owner chain RN provides may skip levels (intermediate composite components
|
|
9
|
+
* are omitted by RN's implementation), so the panel breadcrumb jumps from Text
|
|
10
|
+
* straight to an ancestor, missing intermediate levels.
|
|
11
|
+
*
|
|
12
|
+
* Adaptation point 3 (Paper, RN <= 0.72): RN Paper's createHierarchy only provides
|
|
13
|
+
* the props of the "nearest host descendant" (getHostProps) for each owner-chain
|
|
14
|
+
* fiber, never the fiber itself. Wrapper components such as RN's Button/Touchable
|
|
15
|
+
* that do not forward props have the babel-injected __callerSource on their
|
|
16
|
+
* composite fiber, but their inner host elements (rendered from node_modules) do
|
|
17
|
+
* not — so the nearest item with codeInfo on the tapped chain jumps straight to the
|
|
18
|
+
* business screen component. Fix: use the container view's
|
|
19
|
+
* _internalFiberInstanceHandleDEV (fiber) plus viewData.touchedViewTag (native tag)
|
|
20
|
+
* to find the tapped host fiber, then rebuild the hierarchy along the owner chain
|
|
21
|
+
* (composite components included), with each item reading fiber.memoizedProps
|
|
22
|
+
* directly, so __callerSource on <Button> becomes visible.
|
|
23
|
+
*
|
|
24
|
+
* Implementation (both architectures share one rebuild path):
|
|
25
|
+
* - Fabric: start from viewData.closestInstance (the real fiber); pickSourceFiber
|
|
26
|
+
* finds the target fiber carrying __callerSource;
|
|
27
|
+
* - Paper: walk up from inspectedView._internalFiberInstanceHandleDEV to the root,
|
|
28
|
+
* find the host fiber whose stateNode._nativeTag === touchedViewTag, then walk
|
|
29
|
+
* up .return for the nearest fiber carrying __callerSource (subtree DFS fallback);
|
|
30
|
+
* - then collect the full composite chain via _debugOwner and rebuild the
|
|
31
|
+
* hierarchy (host components included);
|
|
32
|
+
* - each item's getInspectorData returns {props: fiber.memoizedProps, measure,
|
|
33
|
+
* fiber}, with measure taken from the first host node (stateNode) in that
|
|
34
|
+
* fiber's subtree.
|
|
35
|
+
*/
|
|
36
|
+
const mod = require('__rn_dev_inspector_original_impl__');
|
|
37
|
+
const original = mod.default || mod;
|
|
38
|
+
|
|
39
|
+
function nameOfFiber(fiber) {
|
|
40
|
+
try {
|
|
41
|
+
const t = fiber.type;
|
|
42
|
+
if (typeof t === 'string') return t;
|
|
43
|
+
if (typeof t === 'function') return t.displayName || t.name || null;
|
|
44
|
+
if (t && typeof t === 'object') {
|
|
45
|
+
return t.displayName || (t.render && (t.render.displayName || t.render.name)) || null;
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Shared bounded DFS over a fiber subtree. visit(node) returns a truthy value
|
|
52
|
+
// to stop the walk with that result, or a falsy value to keep going. maxDepth
|
|
53
|
+
// (levels below root, undefined = unlimited) and maxVisited mirror the caps
|
|
54
|
+
// the individual walkers used before they were deduplicated.
|
|
55
|
+
function walkFiberTree(root, maxVisited, visit, maxDepth) {
|
|
56
|
+
try {
|
|
57
|
+
let found = null;
|
|
58
|
+
let visited = 0;
|
|
59
|
+
(function dfs(node, depth) {
|
|
60
|
+
if (found || !node || visited > maxVisited) return;
|
|
61
|
+
if (maxDepth !== undefined && depth > maxDepth) return;
|
|
62
|
+
visited++;
|
|
63
|
+
const result = visit(node);
|
|
64
|
+
if (result) {
|
|
65
|
+
found = result;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
dfs(node.child, depth + 1);
|
|
69
|
+
if (!found) dfs(node.sibling, depth + 1);
|
|
70
|
+
})(root, 0);
|
|
71
|
+
return found;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function pickSourceFiber(fiber) {
|
|
78
|
+
try {
|
|
79
|
+
if (fiber && fiber.memoizedProps && fiber.memoizedProps.__callerSource) {
|
|
80
|
+
return fiber;
|
|
81
|
+
}
|
|
82
|
+
// DFS from the child (the root fiber itself was already checked above);
|
|
83
|
+
// depth cap 11 = 12 levels below the child, same as before deduplication
|
|
84
|
+
const found = walkFiberTree(
|
|
85
|
+
fiber && fiber.child,
|
|
86
|
+
300,
|
|
87
|
+
node => {
|
|
88
|
+
const p = node.memoizedProps;
|
|
89
|
+
return p && p.__callerSource ? node : null;
|
|
90
|
+
},
|
|
91
|
+
11
|
|
92
|
+
);
|
|
93
|
+
return found || fiber;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return fiber;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Paper: on the parent chain (.return) of the tapped host fiber, find the
|
|
100
|
+
// nearest fiber carrying __callerSource. Wrapper component elements written in
|
|
101
|
+
// business JSX, like <Button>, get __callerSource injected by babel, which
|
|
102
|
+
// lands on the wrapper's composite fiber memoizedProps, while its inner host
|
|
103
|
+
// elements (rendered from node_modules) have none — so the first hop up from
|
|
104
|
+
// the host hits it.
|
|
105
|
+
function pickSourceUpChain(fiber) {
|
|
106
|
+
try {
|
|
107
|
+
let f = fiber;
|
|
108
|
+
let n = 0;
|
|
109
|
+
while (f && typeof f.tag === 'number' && n < 40) {
|
|
110
|
+
if (parseCallerSourceRaw(f.memoizedProps)) return f;
|
|
111
|
+
f = f.return;
|
|
112
|
+
n++;
|
|
113
|
+
}
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Paper: find the tapped host fiber in the subtree of the container fiber
|
|
119
|
+
// (walked up to the root). On Paper a host fiber's stateNode is a
|
|
120
|
+
// ReactNativeFiberHostComponent whose _nativeTag is the native view tag,
|
|
121
|
+
// matching viewData.touchedViewTag.
|
|
122
|
+
function findTouchedHostFiber(containerFiber, nativeTag) {
|
|
123
|
+
try {
|
|
124
|
+
// Walk up to the root, covering nodes rendered outside the container's
|
|
125
|
+
// subtree (Modal/Portal and friends)
|
|
126
|
+
let root = containerFiber;
|
|
127
|
+
let up = 0;
|
|
128
|
+
while (root && root.return && up < 200) {
|
|
129
|
+
root = root.return;
|
|
130
|
+
up++;
|
|
131
|
+
}
|
|
132
|
+
return walkFiberTree(root, 5000, node => {
|
|
133
|
+
if (node.tag === 5 && node.stateNode) {
|
|
134
|
+
const sn = node.stateNode;
|
|
135
|
+
const tag =
|
|
136
|
+
sn._nativeTag != null ? sn._nativeTag : typeof sn.getTag === 'function' ? sn.getTag() : null;
|
|
137
|
+
return tag === nativeTag ? node : null;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
});
|
|
141
|
+
} catch (e) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// First host node (tag 5 = HostComponent) within this fiber's subtree.
|
|
147
|
+
// A host fiber's stateNode carries measure(cb) itself (Fabric wraps it on the
|
|
148
|
+
// ShadowNode side, Paper uses ReactNativeFiberHostComponent — both go through
|
|
149
|
+
// UIManager.measure), so require('react-native') is not needed here (the shim
|
|
150
|
+
// lives outside the project directory and Metro would fail to resolve it).
|
|
151
|
+
function findHostStateNode(fiber) {
|
|
152
|
+
return walkFiberTree(fiber, 300, node => {
|
|
153
|
+
return node.tag === 5 && node.stateNode ? node.stateNode : null;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function measureFor(stateNode) {
|
|
158
|
+
return function measure(callback) {
|
|
159
|
+
if (!stateNode || typeof stateNode.measure !== 'function') return;
|
|
160
|
+
try {
|
|
161
|
+
stateNode.measure((x, y, width, height, pageX, pageY) => {
|
|
162
|
+
callback(x, y, width, height, pageX, pageY);
|
|
163
|
+
});
|
|
164
|
+
} catch (e) {
|
|
165
|
+
// Node may already be unmounted; ignore
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Composite components named like RN native components (e.g. a business
|
|
171
|
+
// ui/Text wrapper) must be renamed, otherwise core's isUserComponentName
|
|
172
|
+
// filters them out as native and the hierarchy loses a level
|
|
173
|
+
const NATIVE_NAMES = new Set([
|
|
174
|
+
'View',
|
|
175
|
+
'Text',
|
|
176
|
+
'TouchableOpacity',
|
|
177
|
+
'ScrollView',
|
|
178
|
+
'Image',
|
|
179
|
+
'TextInput',
|
|
180
|
+
'Animated',
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// __callerSource may be an object {fileName,lineNumber,columnNumber} or a JSON string
|
|
184
|
+
function parseCallerSourceRaw(props) {
|
|
185
|
+
try {
|
|
186
|
+
const cs = props && props.__callerSource;
|
|
187
|
+
if (!cs) return null;
|
|
188
|
+
return typeof cs === 'string' ? JSON.parse(cs) : cs;
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function baseNameToComponentName(fileName) {
|
|
195
|
+
if (!fileName) return null;
|
|
196
|
+
const base = String(fileName).replace(/\\/g, '/').split('/').pop() || '';
|
|
197
|
+
let stem = base.replace(/\.(tsx?|jsx?)$/, '');
|
|
198
|
+
if (stem === 'index') {
|
|
199
|
+
// index.tsx: use the parent directory name
|
|
200
|
+
const dir = String(fileName).replace(/\\/g, '/').split('/');
|
|
201
|
+
stem = dir[dir.length - 2] || stem;
|
|
202
|
+
}
|
|
203
|
+
if (!stem) return null;
|
|
204
|
+
return stem.charAt(0).toUpperCase() + stem.slice(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Anonymous composite components (typically React.forwardRef((props)=>...)
|
|
208
|
+
// with no function name, common in business code): derive the component name
|
|
209
|
+
// from the file name of the first child carrying __callerSource inside its
|
|
210
|
+
// render, e.g. <AnimatedBaseButton> at button.tsx:161 -> "Button"
|
|
211
|
+
function deriveAnonymousName(fiber) {
|
|
212
|
+
try {
|
|
213
|
+
const fileName = walkFiberTree(fiber && fiber.child, 60, node => {
|
|
214
|
+
const parsed = parseCallerSourceRaw(node.memoizedProps);
|
|
215
|
+
return parsed && parsed.fileName ? parsed.fileName : null;
|
|
216
|
+
});
|
|
217
|
+
return fileName ? baseNameToComponentName(fileName) : null;
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function displayNameForFiber(fiber, name) {
|
|
224
|
+
const t = fiber.type;
|
|
225
|
+
const isComposite = typeof t === 'function' || (t && typeof t === 'object');
|
|
226
|
+
if (isComposite && NATIVE_NAMES.has(name)) {
|
|
227
|
+
return `${name} (custom)`;
|
|
228
|
+
}
|
|
229
|
+
return name;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---- Source filtering (breadcrumb shows only business-source levels) ----
|
|
233
|
+
// The babel plugin does not inject __callerSource into node_modules, so "the
|
|
234
|
+
// fiber's own props carry __callerSource" implies the element was written in
|
|
235
|
+
// business-source JSX. Wrapper components instantiated inside third-party
|
|
236
|
+
// libraries (e.g. ViewHolderCollection / ViewHolderInternal) lack the marker
|
|
237
|
+
// and are hidden from the breadcrumb; the deepest tapped element is always
|
|
238
|
+
// kept, so picking itself is unaffected.
|
|
239
|
+
const PROJECT_SOURCE_ONLY = true;
|
|
240
|
+
|
|
241
|
+
function isProjectSourceFiber(fiber) {
|
|
242
|
+
const parsed = parseCallerSourceRaw(fiber.memoizedProps);
|
|
243
|
+
if (!parsed || !parsed.fileName) return false;
|
|
244
|
+
return !String(parsed.fileName).includes('node_modules');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Some library wrappers still get instantiated in business
|
|
248
|
+
// JSX (e.g. nativewind's styled(Pressable), written on a button.tsx render
|
|
249
|
+
// line, so the fiber carries __callerSource) and a path check would mistake
|
|
250
|
+
// them for business source — filter them out by name as a fallback;
|
|
251
|
+
// 'Xxx (custom)' is a business marker and is never filtered.
|
|
252
|
+
// Note the prefix is "AnimatedComponent", not "Animated" — business
|
|
253
|
+
// components may legitimately be named AnimatedXxx.
|
|
254
|
+
const LIB_WRAPPER_NAMES = new Set([
|
|
255
|
+
'Pressable', 'TouchableHighlight', 'TouchableNativeFeedback',
|
|
256
|
+
'TouchableWithoutFeedback', 'FlatList', 'SectionList',
|
|
257
|
+
'KeyboardAvoidingView', 'Switch', 'StatusBar', 'MaskedView',
|
|
258
|
+
'RefreshControl', 'ActivityIndicator', 'ProgressBar',
|
|
259
|
+
'GestureHandlerButton',
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
function isLibraryWrapperName(name) {
|
|
263
|
+
if (!name) return false;
|
|
264
|
+
if (name.endsWith(' (custom)')) return false;
|
|
265
|
+
if (name.includes('(')) return true; // wrapper levels like AnimatedComponent(Pressable)
|
|
266
|
+
if (name.startsWith('AnimatedComponent')) return true;
|
|
267
|
+
return LIB_WRAPPER_NAMES.has(name);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Collect the full owner chain up from the source fiber via _debugOwner and
|
|
271
|
+
// rebuild the hierarchy (shared by Fabric and Paper; on Paper the composite
|
|
272
|
+
// fibers of wrappers like Button join the chain so core can read the
|
|
273
|
+
// __callerSource on their memoizedProps)
|
|
274
|
+
function rebuildHierarchyFromSource(sourceFiber) {
|
|
275
|
+
const chain = [];
|
|
276
|
+
let f = sourceFiber;
|
|
277
|
+
while (f && typeof f.tag === 'number' && chain.length < 40) {
|
|
278
|
+
chain.push(f);
|
|
279
|
+
f = f._debugOwner;
|
|
280
|
+
}
|
|
281
|
+
chain.reverse(); // [0] = closest to root, last = deepest (the tapped element)
|
|
282
|
+
return chain
|
|
283
|
+
.map((fiber, idx) => {
|
|
284
|
+
let raw = nameOfFiber(fiber);
|
|
285
|
+
const t = fiber.type;
|
|
286
|
+
const isComposite = typeof t === 'function' || (t && typeof t === 'object');
|
|
287
|
+
if (!raw && isComposite) {
|
|
288
|
+
raw = deriveAnonymousName(fiber);
|
|
289
|
+
}
|
|
290
|
+
if (!raw) return null;
|
|
291
|
+
// Hide intermediate levels that are not business source or are library
|
|
292
|
+
// wrappers (last item = tapped element, always kept)
|
|
293
|
+
if (
|
|
294
|
+
PROJECT_SOURCE_ONLY &&
|
|
295
|
+
idx < chain.length - 1 &&
|
|
296
|
+
(!isProjectSourceFiber(fiber) || isLibraryWrapperName(raw))
|
|
297
|
+
) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
// nativewind's CssInterop wrapper is pure forwarding, and its
|
|
301
|
+
// __callerSource points inside the wrapper implementation (e.g. a
|
|
302
|
+
// ui/Text/index.tsx render line) — meaningless to users, skip it
|
|
303
|
+
if (raw.startsWith('CssInterop')) return null;
|
|
304
|
+
return {
|
|
305
|
+
name: displayNameForFiber(fiber, raw),
|
|
306
|
+
getInspectorData: () => ({
|
|
307
|
+
props: fiber.memoizedProps,
|
|
308
|
+
measure: measureFor(findHostStateNode(fiber)),
|
|
309
|
+
fiber,
|
|
310
|
+
}),
|
|
311
|
+
};
|
|
312
|
+
})
|
|
313
|
+
.filter(Boolean);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function getInspectorDataForViewAtPoint(inspectedView, locationX, locationY, callback) {
|
|
317
|
+
original(inspectedView, locationX, locationY, viewData => {
|
|
318
|
+
try {
|
|
319
|
+
if (viewData && viewData.closestInstance && typeof viewData.closestInstance.tag === 'number') {
|
|
320
|
+
// Fabric (RN 0.73+): closestInstance is the real fiber
|
|
321
|
+
const sourceFiber = pickSourceFiber(viewData.closestInstance);
|
|
322
|
+
viewData.hierarchy = rebuildHierarchyFromSource(sourceFiber);
|
|
323
|
+
} else if (
|
|
324
|
+
viewData &&
|
|
325
|
+
typeof viewData.touchedViewTag === 'number' &&
|
|
326
|
+
inspectedView &&
|
|
327
|
+
inspectedView._internalFiberInstanceHandleDEV &&
|
|
328
|
+
typeof inspectedView._internalFiberInstanceHandleDEV.tag === 'number'
|
|
329
|
+
) {
|
|
330
|
+
// Paper (RN <= 0.72): RN provides only host-descendant props, not the
|
|
331
|
+
// fiber. Look the host fiber up via touchedViewTag, then walk the
|
|
332
|
+
// parent chain for the nearest business element fiber
|
|
333
|
+
const touched = findTouchedHostFiber(
|
|
334
|
+
inspectedView._internalFiberInstanceHandleDEV,
|
|
335
|
+
viewData.touchedViewTag
|
|
336
|
+
);
|
|
337
|
+
if (touched) {
|
|
338
|
+
const sourceFiber = pickSourceUpChain(touched) || pickSourceFiber(touched);
|
|
339
|
+
viewData.hierarchy = rebuildHierarchyFromSource(sourceFiber);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (__DEV__) {
|
|
343
|
+
try {
|
|
344
|
+
console.log(
|
|
345
|
+
'[rn-inspector-adapter] chain:',
|
|
346
|
+
(viewData && viewData.hierarchy ? viewData.hierarchy : []).map(h => h.name).join(' > ')
|
|
347
|
+
);
|
|
348
|
+
} catch (e) {}
|
|
349
|
+
}
|
|
350
|
+
} catch (e) {
|
|
351
|
+
if (__DEV__) console.log('[rn-inspector-adapter] augment error', e && e.message);
|
|
352
|
+
}
|
|
353
|
+
callback(viewData);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
module.exports = getInspectorDataForViewAtPoint;
|
|
358
|
+
module.exports.default = getInspectorDataForViewAtPoint;
|