react-code-locator 0.1.18 → 0.2.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/README.md +86 -38
- package/dist/client.cjs +1 -399
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -371
- package/dist/client.js.map +1 -1
- package/dist/esbuild.cjs +29 -7149
- package/dist/esbuild.cjs.map +1 -1
- package/dist/esbuild.d.cts +1 -1
- package/dist/esbuild.d.ts +1 -1
- package/dist/esbuild.js +29 -7122
- package/dist/esbuild.js.map +1 -1
- package/dist/index.cjs +32 -7504
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +32 -7471
- package/dist/index.js.map +1 -1
- package/dist/swc.cjs +29 -7119
- package/dist/swc.cjs.map +1 -1
- package/dist/swc.d.cts +1 -1
- package/dist/swc.d.ts +1 -1
- package/dist/swc.js +29 -7090
- package/dist/swc.js.map +1 -1
- package/dist/{transform-CXh-m5Ez.d.cts → transform-z2n_1yc5.d.cts} +2 -2
- package/dist/{transform-CXh-m5Ez.d.ts → transform-z2n_1yc5.d.ts} +2 -2
- package/dist/unplugin.cjs +32 -7138
- package/dist/unplugin.cjs.map +1 -1
- package/dist/unplugin.d.cts +15 -6
- package/dist/unplugin.d.ts +15 -6
- package/dist/unplugin.js +32 -7108
- package/dist/unplugin.js.map +1 -1
- package/dist/webpack.cjs +40 -0
- package/dist/webpack.cjs.map +1 -0
- package/dist/webpack.d.cts +16 -0
- package/dist/webpack.d.ts +16 -0
- package/dist/webpack.js +40 -0
- package/dist/webpack.js.map +1 -0
- package/package.json +13 -2
- package/dist/vite.cjs +0 -7200
- package/dist/vite.cjs.map +0 -1
- package/dist/vite.d.cts +0 -52
- package/dist/vite.d.ts +0 -52
- package/dist/vite.js +0 -7169
- package/dist/vite.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,21 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
개발 중인 React 앱에서 요소를 `Shift + Click`하면 해당 UI와 연결된 소스 위치를 찾는 패키지입니다.
|
|
4
4
|
|
|
5
|
-
- React element 생성 시 source metadata를
|
|
6
|
-
-
|
|
7
|
-
- **Universal**:
|
|
5
|
+
- React element 생성 시 source metadata를 주입하고, 브라우저 런타임에서 Fiber를 따라 위치를 계산합니다.
|
|
6
|
+
- **완전 번들링**: acorn 기반 순수 JS 파서를 내장 — 별도 파서 설치 불필요
|
|
7
|
+
- **Universal**: Vite, Webpack, Rollup, esbuild, Rspack 모두 지원
|
|
8
|
+
- **개발 전용**: 프로덕션 빌드에 영향 없음
|
|
8
9
|
|
|
9
10
|
## 설치
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
npm i -D react-code-locator
|
|
13
|
+
npm i -D react-code-locator
|
|
13
14
|
```
|
|
14
15
|
|
|
16
|
+
> Vite / Rollup / esbuild / Rspack 플러그인을 사용하려면 `unplugin`도 필요합니다.
|
|
17
|
+
>
|
|
18
|
+
> ```bash
|
|
19
|
+
> npm i -D unplugin
|
|
20
|
+
> ```
|
|
21
|
+
>
|
|
22
|
+
> Webpack은 unplugin 없이 동작합니다.
|
|
23
|
+
|
|
15
24
|
## 빠른 시작
|
|
16
25
|
|
|
17
26
|
### Vite
|
|
18
27
|
|
|
28
|
+
`vitePlugin`은 소스 transform + 클라이언트 런타임 자동 주입을 모두 처리합니다.
|
|
29
|
+
|
|
19
30
|
```ts
|
|
31
|
+
// vite.config.ts
|
|
20
32
|
import { defineConfig } from "vite";
|
|
21
33
|
import react from "@vitejs/plugin-react";
|
|
22
34
|
import { vitePlugin } from "react-code-locator";
|
|
@@ -29,51 +41,88 @@ export default defineConfig({
|
|
|
29
41
|
});
|
|
30
42
|
```
|
|
31
43
|
|
|
32
|
-
###
|
|
44
|
+
### Next.js (Webpack)
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
// next.config.js
|
|
48
|
+
const { webpackPlugin } = require("react-code-locator/webpack");
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
webpack(config, { dev }) {
|
|
52
|
+
if (dev) {
|
|
53
|
+
config.plugins.push(webpackPlugin());
|
|
54
|
+
}
|
|
55
|
+
return config;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Create React App
|
|
33
61
|
|
|
34
62
|
```js
|
|
35
63
|
// config-overrides.js
|
|
36
|
-
const { webpackPlugin } = require(
|
|
64
|
+
const { webpackPlugin } = require("react-code-locator/webpack");
|
|
37
65
|
|
|
38
66
|
module.exports = {
|
|
39
|
-
webpack
|
|
40
|
-
if (env ===
|
|
67
|
+
webpack(config, env) {
|
|
68
|
+
if (env === "development") {
|
|
41
69
|
config.plugins.push(webpackPlugin());
|
|
42
70
|
}
|
|
43
71
|
return config;
|
|
44
|
-
}
|
|
72
|
+
},
|
|
45
73
|
};
|
|
46
74
|
```
|
|
47
75
|
|
|
48
76
|
### Rollup
|
|
49
77
|
|
|
50
78
|
```js
|
|
51
|
-
|
|
79
|
+
// rollup.config.js
|
|
80
|
+
import { rollupPlugin } from "react-code-locator";
|
|
52
81
|
|
|
53
82
|
export default {
|
|
54
|
-
plugins: [rollupPlugin()]
|
|
83
|
+
plugins: [rollupPlugin()],
|
|
55
84
|
};
|
|
56
85
|
```
|
|
57
86
|
|
|
58
87
|
### esbuild
|
|
59
88
|
|
|
60
89
|
```js
|
|
61
|
-
import { esbuildPlugin } from
|
|
90
|
+
import { esbuildPlugin } from "react-code-locator";
|
|
62
91
|
|
|
63
|
-
|
|
64
|
-
plugins: [esbuildPlugin()]
|
|
92
|
+
await esbuild.build({
|
|
93
|
+
plugins: [esbuildPlugin()],
|
|
65
94
|
});
|
|
66
95
|
```
|
|
67
96
|
|
|
97
|
+
### Rspack
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
// rspack.config.js
|
|
101
|
+
const { rspackPlugin } = require("react-code-locator");
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
plugins: [rspackPlugin()],
|
|
105
|
+
};
|
|
106
|
+
```
|
|
107
|
+
|
|
68
108
|
## 옵션
|
|
69
109
|
|
|
110
|
+
모든 플러그인이 동일한 옵션을 공유합니다.
|
|
111
|
+
|
|
70
112
|
```ts
|
|
71
113
|
vitePlugin({
|
|
72
|
-
|
|
114
|
+
// 소스 transform 옵션
|
|
115
|
+
projectRoot: process.cwd(), // 프로젝트 루트 (상대 경로 기준)
|
|
73
116
|
injectComponentSource: true, // 컴포넌트 정의에 소스 주입
|
|
74
117
|
injectJsxSource: true, // JSX 호출부에 소스 주입
|
|
75
118
|
include: /\.[jt]sx$/, // 포함할 파일 패턴
|
|
76
119
|
exclude: /node_modules/, // 제외할 파일 패턴
|
|
120
|
+
|
|
121
|
+
// Vite 전용 옵션
|
|
122
|
+
injectClient: true, // 클라이언트 런타임 자동 주입 (기본값: true)
|
|
123
|
+
locator: { // 런타임 옵션
|
|
124
|
+
triggerKey: "shift", // 트리거 키 (기본값: "shift")
|
|
125
|
+
},
|
|
77
126
|
});
|
|
78
127
|
```
|
|
79
128
|
|
|
@@ -81,43 +130,42 @@ vitePlugin({
|
|
|
81
130
|
|
|
82
131
|
개발 서버에서 `Shift + Click`하면 브라우저 콘솔에 소스 위치가 출력됩니다.
|
|
83
132
|
|
|
84
|
-
```
|
|
133
|
+
```
|
|
85
134
|
[react-code-locator] src/components/Button.tsx:14:1
|
|
86
135
|
```
|
|
87
136
|
|
|
88
|
-
|
|
137
|
+
결과를 클릭하면 클립보드에 복사됩니다.
|
|
89
138
|
|
|
90
|
-
|
|
91
|
-
- `Alt + 1`: direct 모드 (JSX 호출부)
|
|
92
|
-
- `Alt + 2`: screen 모드 (화면 컴포넌트)
|
|
93
|
-
- `Alt + 3`: implementation 모드 (구현체)
|
|
139
|
+
### 단축키
|
|
94
140
|
|
|
95
|
-
|
|
141
|
+
| 키 | 동작 |
|
|
142
|
+
|----|------|
|
|
143
|
+
| `Shift + Click` | 소스 위치 찾기 |
|
|
144
|
+
| `Alt + 1` | direct 모드 (JSX 호출부 위치) |
|
|
145
|
+
| `Alt + 2` | screen 모드 (화면에 보이는 컴포넌트) |
|
|
146
|
+
| `Alt + 3` | implementation 모드 (구현체 위치) |
|
|
96
147
|
|
|
97
|
-
|
|
148
|
+
## 수동 설정 (고급)
|
|
98
149
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
자동 주입을 사용하지 않고 직접 런타임을 초기화하려면:
|
|
150
|
+
Vite가 아닌 환경에서는 클라이언트 런타임을 직접 초기화해야 합니다.
|
|
102
151
|
|
|
103
152
|
```ts
|
|
104
153
|
import { enableReactComponentJump } from "react-code-locator/client";
|
|
105
154
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
}
|
|
155
|
+
enableReactComponentJump({
|
|
156
|
+
triggerKey: "shift",
|
|
157
|
+
onLocate(result) {
|
|
158
|
+
console.log("Source:", result.source);
|
|
159
|
+
},
|
|
160
|
+
});
|
|
114
161
|
```
|
|
115
162
|
|
|
116
|
-
##
|
|
163
|
+
## 알려진 제한사항
|
|
117
164
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
165
|
+
- **React Native 미지원**: DOM API에 의존합니다.
|
|
166
|
+
- **Turbopack 미지원**: Next.js 13+의 Turbopack은 현재 지원되지 않습니다.
|
|
167
|
+
- **TSX generic arrow function**: `.tsx` 파일에서 `<T,>` 형태의 제네릭 화살표 함수가 있는 파일은 transform이 스킵됩니다. (`function` 선언형이나 `.ts` 파일에서는 정상 동작합니다.)
|
|
168
|
+
- **개발 전용**: `NODE_ENV=development` 환경에서만 사용하세요.
|
|
121
169
|
|
|
122
170
|
## License
|
|
123
171
|
|
package/dist/client.cjs
CHANGED
|
@@ -1,400 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/client.ts
|
|
21
|
-
var client_exports = {};
|
|
22
|
-
__export(client_exports, {
|
|
23
|
-
enableReactComponentJump: () => enableReactComponentJump,
|
|
24
|
-
locateComponentSource: () => locateComponentSource
|
|
25
|
-
});
|
|
26
|
-
module.exports = __toCommonJS(client_exports);
|
|
27
|
-
|
|
28
|
-
// src/constants.ts
|
|
29
|
-
var SOURCE_PROP = "__componentSourceLoc";
|
|
30
|
-
var JSX_SOURCE_PROP = "$componentSourceLoc";
|
|
31
|
-
var JSX_SOURCE_REGISTRY_SYMBOL = "react-code-locator.jsxSourceRegistry";
|
|
32
|
-
|
|
33
|
-
// src/sourceMetadata.ts
|
|
34
|
-
function normalizeSlashes(value) {
|
|
35
|
-
return value.replace(/\\/g, "/");
|
|
36
|
-
}
|
|
37
|
-
function trimTrailingSlash(value) {
|
|
38
|
-
return value.replace(/\/+$/, "");
|
|
39
|
-
}
|
|
40
|
-
function splitPathSegments(value) {
|
|
41
|
-
return normalizeSlashes(value).split("/").filter(Boolean);
|
|
42
|
-
}
|
|
43
|
-
function computeRelativePath(fromPath, toPath) {
|
|
44
|
-
const fromSegments = splitPathSegments(fromPath);
|
|
45
|
-
const toSegments = splitPathSegments(toPath);
|
|
46
|
-
let sharedIndex = 0;
|
|
47
|
-
while (sharedIndex < fromSegments.length && sharedIndex < toSegments.length && fromSegments[sharedIndex] === toSegments[sharedIndex]) {
|
|
48
|
-
sharedIndex += 1;
|
|
49
|
-
}
|
|
50
|
-
const upSegments = new Array(Math.max(0, fromSegments.length - sharedIndex)).fill("..");
|
|
51
|
-
const downSegments = toSegments.slice(sharedIndex);
|
|
52
|
-
const relativeSegments = [...upSegments, ...downSegments];
|
|
53
|
-
return relativeSegments.length > 0 ? relativeSegments.join("/") : ".";
|
|
54
|
-
}
|
|
55
|
-
function normalizeProjectRoot(projectRoot) {
|
|
56
|
-
if (projectRoot) {
|
|
57
|
-
return trimTrailingSlash(normalizeSlashes(projectRoot));
|
|
58
|
-
}
|
|
59
|
-
return "";
|
|
60
|
-
}
|
|
61
|
-
function getSourceFile(source) {
|
|
62
|
-
if (!source) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
const match = source.match(/^(.*):\d+:\d+$/);
|
|
66
|
-
return match?.[1] ?? null;
|
|
67
|
-
}
|
|
68
|
-
function isProjectLocalFile(filename, projectRoot) {
|
|
69
|
-
if (!filename) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const root = normalizeProjectRoot(projectRoot);
|
|
73
|
-
const normalizedFilename = normalizeSlashes(filename);
|
|
74
|
-
if (!root) {
|
|
75
|
-
return !normalizedFilename.startsWith("../") && !normalizedFilename.startsWith("/") && !/^[A-Za-z]:\//.test(normalizedFilename);
|
|
76
|
-
}
|
|
77
|
-
if (normalizedFilename.startsWith(`${root}/`) || normalizedFilename === root) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
const relativePath = computeRelativePath(root, normalizedFilename);
|
|
81
|
-
return !relativePath.startsWith("../");
|
|
82
|
-
}
|
|
83
|
-
function isProjectLocalSource(source, projectRoot) {
|
|
84
|
-
const file = getSourceFile(source);
|
|
85
|
-
return isProjectLocalFile(file ?? void 0, projectRoot);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// src/runtime.ts
|
|
89
|
-
function isTriggerPressed(event, triggerKey) {
|
|
90
|
-
if (triggerKey === "none") {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (triggerKey === "alt") {
|
|
94
|
-
return event.altKey;
|
|
95
|
-
}
|
|
96
|
-
if (triggerKey === "meta") {
|
|
97
|
-
return event.metaKey;
|
|
98
|
-
}
|
|
99
|
-
if (triggerKey === "ctrl") {
|
|
100
|
-
return event.ctrlKey;
|
|
101
|
-
}
|
|
102
|
-
return event.shiftKey;
|
|
103
|
-
}
|
|
104
|
-
function getReactFiberKey(element) {
|
|
105
|
-
return Object.keys(element).find((key) => key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$"));
|
|
106
|
-
}
|
|
107
|
-
function getClosestReactFiber(target) {
|
|
108
|
-
let current = target;
|
|
109
|
-
while (current) {
|
|
110
|
-
const fiberKey = getReactFiberKey(current);
|
|
111
|
-
if (fiberKey) {
|
|
112
|
-
return current[fiberKey];
|
|
113
|
-
}
|
|
114
|
-
current = current.parentElement;
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
function getSourceFromType(type) {
|
|
119
|
-
if (!type) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
if (typeof type === "function") {
|
|
123
|
-
const source2 = type[SOURCE_PROP];
|
|
124
|
-
return typeof source2 === "string" ? source2 : null;
|
|
125
|
-
}
|
|
126
|
-
if (typeof type !== "object") {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
const record = type;
|
|
130
|
-
const source = record[SOURCE_PROP] ?? record.type?.[SOURCE_PROP] ?? record.render?.[SOURCE_PROP];
|
|
131
|
-
return typeof source === "string" ? source : null;
|
|
132
|
-
}
|
|
133
|
-
function getSourceFromProps(props) {
|
|
134
|
-
if (props && typeof props === "object") {
|
|
135
|
-
const registry = globalThis[Symbol.for(JSX_SOURCE_REGISTRY_SYMBOL)];
|
|
136
|
-
if (registry instanceof WeakMap) {
|
|
137
|
-
const intrinsicSource = registry.get(props);
|
|
138
|
-
if (typeof intrinsicSource === "string") {
|
|
139
|
-
return intrinsicSource;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
const source = props?.[JSX_SOURCE_PROP];
|
|
144
|
-
return typeof source === "string" ? source : null;
|
|
145
|
-
}
|
|
146
|
-
function resolveComponentSourceFromFiber(fiber) {
|
|
147
|
-
let current = fiber;
|
|
148
|
-
while (current) {
|
|
149
|
-
const source = getSourceFromType(current.type) ?? getSourceFromType(current.elementType);
|
|
150
|
-
if (source) {
|
|
151
|
-
return source;
|
|
152
|
-
}
|
|
153
|
-
current = current.return ?? null;
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
function getDirectDebugSource(fiber) {
|
|
158
|
-
const debugSource = fiber?._debugSource;
|
|
159
|
-
if (debugSource?.fileName && typeof debugSource.lineNumber === "number") {
|
|
160
|
-
return `${debugSource.fileName.replace(/\\/g, "/")}:${debugSource.lineNumber}:${debugSource.columnNumber ?? 1}`;
|
|
161
|
-
}
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
function resolveSourceCandidates(fiber) {
|
|
165
|
-
let current = fiber;
|
|
166
|
-
const jsxCandidates = [];
|
|
167
|
-
const componentCandidates = [];
|
|
168
|
-
while (current) {
|
|
169
|
-
const jsxSource = getSourceFromProps(current.pendingProps) ?? getSourceFromProps(current.memoizedProps) ?? getDirectDebugSource(current);
|
|
170
|
-
if (jsxSource) {
|
|
171
|
-
const file = getSourceFile(jsxSource);
|
|
172
|
-
if (file && !jsxCandidates.some((candidate) => candidate.source === jsxSource)) {
|
|
173
|
-
jsxCandidates.push({ source: jsxSource, file });
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const componentSource = getSourceFromType(current.type) ?? getSourceFromType(current.elementType);
|
|
177
|
-
if (componentSource) {
|
|
178
|
-
const file = getSourceFile(componentSource);
|
|
179
|
-
if (file && !componentCandidates.some((candidate) => candidate.source === componentSource)) {
|
|
180
|
-
componentCandidates.push({ source: componentSource, file });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
current = current.return ?? null;
|
|
184
|
-
}
|
|
185
|
-
const direct = jsxCandidates[0]?.source ?? null;
|
|
186
|
-
const nearestProjectLocalComponentFile = componentCandidates.find((candidate) => isProjectLocalSource(candidate.source))?.file;
|
|
187
|
-
let screen = null;
|
|
188
|
-
if (nearestProjectLocalComponentFile) {
|
|
189
|
-
const matchingJsxCandidate = jsxCandidates.find((candidate) => candidate.file === nearestProjectLocalComponentFile);
|
|
190
|
-
if (matchingJsxCandidate) {
|
|
191
|
-
screen = matchingJsxCandidate.source;
|
|
192
|
-
} else {
|
|
193
|
-
const matchingComponentCandidate = componentCandidates.find(
|
|
194
|
-
(candidate) => candidate.file === nearestProjectLocalComponentFile
|
|
195
|
-
);
|
|
196
|
-
if (matchingComponentCandidate) {
|
|
197
|
-
screen = matchingComponentCandidate.source;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const implementationComponentCandidate = componentCandidates.find((candidate) => !isProjectLocalSource(candidate.source))?.source ?? null;
|
|
202
|
-
const implementationJsxCandidate = jsxCandidates.find((candidate) => !isProjectLocalSource(candidate.source))?.source ?? null;
|
|
203
|
-
const projectLocalJsxCandidate = jsxCandidates.find((candidate) => isProjectLocalSource(candidate.source))?.source ?? null;
|
|
204
|
-
const screenFallback = screen ?? projectLocalJsxCandidate ?? componentCandidates.find((candidate) => isProjectLocalSource(candidate.source))?.source ?? null;
|
|
205
|
-
return {
|
|
206
|
-
direct: direct ?? screenFallback,
|
|
207
|
-
screen: screenFallback,
|
|
208
|
-
implementation: implementationComponentCandidate ?? implementationJsxCandidate ?? screenFallback
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function getModeDescription(mode) {
|
|
212
|
-
if (mode === "direct") {
|
|
213
|
-
return "Direct JSX";
|
|
214
|
-
}
|
|
215
|
-
if (mode === "screen") {
|
|
216
|
-
return "Screen source";
|
|
217
|
-
}
|
|
218
|
-
return "Implementation source";
|
|
219
|
-
}
|
|
220
|
-
function createStatusOverlay(triggerKey) {
|
|
221
|
-
if (typeof document === "undefined") {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
const element = document.createElement("div");
|
|
225
|
-
let copyValue = null;
|
|
226
|
-
let currentMode = "screen";
|
|
227
|
-
let hideTimer = null;
|
|
228
|
-
element.setAttribute("data-react-code-locator", "true");
|
|
229
|
-
Object.assign(element.style, {
|
|
230
|
-
position: "fixed",
|
|
231
|
-
right: "12px",
|
|
232
|
-
bottom: "12px",
|
|
233
|
-
zIndex: "2147483647",
|
|
234
|
-
padding: "8px 10px",
|
|
235
|
-
borderRadius: "8px",
|
|
236
|
-
background: "rgba(17, 24, 39, 0.92)",
|
|
237
|
-
color: "#fff",
|
|
238
|
-
fontSize: "12px",
|
|
239
|
-
lineHeight: "1.4",
|
|
240
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
241
|
-
boxShadow: "0 8px 30px rgba(0, 0, 0, 0.25)",
|
|
242
|
-
pointerEvents: "auto",
|
|
243
|
-
cursor: "pointer",
|
|
244
|
-
maxWidth: "min(70vw, 720px)",
|
|
245
|
-
wordBreak: "break-all",
|
|
246
|
-
opacity: "0",
|
|
247
|
-
transition: "opacity 120ms ease"
|
|
248
|
-
});
|
|
249
|
-
const show = (message, tone) => {
|
|
250
|
-
element.textContent = message;
|
|
251
|
-
element.style.background = tone === "success" ? "rgba(6, 95, 70, 0.92)" : tone === "error" ? "rgba(153, 27, 27, 0.94)" : "rgba(17, 24, 39, 0.92)";
|
|
252
|
-
element.style.opacity = "1";
|
|
253
|
-
element.style.pointerEvents = "auto";
|
|
254
|
-
if (hideTimer) {
|
|
255
|
-
clearTimeout(hideTimer);
|
|
256
|
-
}
|
|
257
|
-
hideTimer = setTimeout(() => {
|
|
258
|
-
element.style.opacity = "0";
|
|
259
|
-
element.style.pointerEvents = "none";
|
|
260
|
-
}, 2e3);
|
|
261
|
-
};
|
|
262
|
-
element.addEventListener("click", async () => {
|
|
263
|
-
if (!copyValue) {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
try {
|
|
267
|
-
await navigator.clipboard.writeText(copyValue);
|
|
268
|
-
show(`[react-code-locator] copied`, "success");
|
|
269
|
-
} catch {
|
|
270
|
-
show(`[react-code-locator] copy failed`, "error");
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
show(`[react-code-locator] enabled (${triggerKey}+click, alt+1/2/3 to switch mode)`, "idle");
|
|
274
|
-
const mount = () => {
|
|
275
|
-
if (!element.isConnected && document.body) {
|
|
276
|
-
document.body.appendChild(element);
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
if (document.body) {
|
|
280
|
-
mount();
|
|
281
|
-
} else {
|
|
282
|
-
document.addEventListener("DOMContentLoaded", mount, { once: true });
|
|
283
|
-
}
|
|
284
|
-
return {
|
|
285
|
-
setStatus(message, tone = "idle") {
|
|
286
|
-
show(message, tone);
|
|
287
|
-
},
|
|
288
|
-
setCopyValue(value) {
|
|
289
|
-
copyValue = value;
|
|
290
|
-
},
|
|
291
|
-
setMode(mode) {
|
|
292
|
-
currentMode = mode;
|
|
293
|
-
show(`[react-code-locator] ${getModeDescription(mode)}`, "idle");
|
|
294
|
-
},
|
|
295
|
-
remove() {
|
|
296
|
-
if (hideTimer) {
|
|
297
|
-
clearTimeout(hideTimer);
|
|
298
|
-
}
|
|
299
|
-
element.remove();
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
function locateComponentSource(target, mode = "screen") {
|
|
304
|
-
const elementTarget = target instanceof Element ? target : target instanceof Node ? target.parentElement : null;
|
|
305
|
-
const fiber = getClosestReactFiber(elementTarget);
|
|
306
|
-
if (!fiber) {
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
const candidates = resolveSourceCandidates(fiber);
|
|
310
|
-
const source = candidates[mode] ?? candidates.screen ?? candidates.direct ?? candidates.implementation;
|
|
311
|
-
if (source) {
|
|
312
|
-
return {
|
|
313
|
-
source,
|
|
314
|
-
mode
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
const componentSource = resolveComponentSourceFromFiber(fiber);
|
|
318
|
-
if (!componentSource) {
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
return {
|
|
322
|
-
source: componentSource,
|
|
323
|
-
mode
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function enableReactComponentJump(options = {}) {
|
|
327
|
-
const overlay = createStatusOverlay(options.triggerKey ?? "shift");
|
|
328
|
-
let currentMode = "screen";
|
|
329
|
-
const {
|
|
330
|
-
triggerKey = "shift",
|
|
331
|
-
onLocate = (result) => {
|
|
332
|
-
console.log(`[react-code-locator] ${result.source}`);
|
|
333
|
-
overlay?.setCopyValue(result.source);
|
|
334
|
-
overlay?.setStatus(`[react-code-locator] ${result.source}`, "success");
|
|
335
|
-
},
|
|
336
|
-
onError = (error) => {
|
|
337
|
-
console.error("[react-code-locator]", error);
|
|
338
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
-
overlay?.setCopyValue(null);
|
|
340
|
-
overlay?.setStatus(`[react-code-locator] ${message}`, "error");
|
|
341
|
-
}
|
|
342
|
-
} = options;
|
|
343
|
-
console.log("[react-code-locator] enabled", { triggerKey });
|
|
344
|
-
const keyHandler = (event) => {
|
|
345
|
-
if (!event.altKey) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (event.code === "Digit1") {
|
|
349
|
-
currentMode = "direct";
|
|
350
|
-
overlay?.setMode(currentMode);
|
|
351
|
-
event.preventDefault();
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (event.code === "Digit2") {
|
|
355
|
-
currentMode = "screen";
|
|
356
|
-
overlay?.setMode(currentMode);
|
|
357
|
-
event.preventDefault();
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (event.code === "Digit3") {
|
|
361
|
-
currentMode = "implementation";
|
|
362
|
-
overlay?.setMode(currentMode);
|
|
363
|
-
event.preventDefault();
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
const handler = (event) => {
|
|
367
|
-
console.log("[react-code-locator] click", {
|
|
368
|
-
triggerKey,
|
|
369
|
-
shiftKey: event.shiftKey,
|
|
370
|
-
altKey: event.altKey,
|
|
371
|
-
ctrlKey: event.ctrlKey,
|
|
372
|
-
metaKey: event.metaKey,
|
|
373
|
-
target: event.target
|
|
374
|
-
});
|
|
375
|
-
if (!isTriggerPressed(event, triggerKey)) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
const result = locateComponentSource(event.target, currentMode);
|
|
379
|
-
if (!result) {
|
|
380
|
-
onError(new Error("No React component source metadata found for clicked element."));
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
event.preventDefault();
|
|
384
|
-
event.stopPropagation();
|
|
385
|
-
onLocate(result);
|
|
386
|
-
};
|
|
387
|
-
document.addEventListener("click", handler, true);
|
|
388
|
-
document.addEventListener("keydown", keyHandler, true);
|
|
389
|
-
return () => {
|
|
390
|
-
document.removeEventListener("click", handler, true);
|
|
391
|
-
document.removeEventListener("keydown", keyHandler, true);
|
|
392
|
-
overlay?.remove();
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
396
|
-
0 && (module.exports = {
|
|
397
|
-
enableReactComponentJump,
|
|
398
|
-
locateComponentSource
|
|
399
|
-
});
|
|
1
|
+
"use strict";var h=Object.defineProperty;var k=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var O=(t,e)=>{for(var n in e)h(t,n,{get:e[n],enumerable:!0})},T=(t,e,n,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let c of P(e))!M.call(t,c)&&c!==n&&h(t,c,{get:()=>e[c],enumerable:!(o=k(e,c))||o.enumerable});return t};var F=t=>T(h({},"__esModule",{value:!0}),t);var X={};O(X,{enableReactComponentJump:()=>v,locateComponentSource:()=>x});module.exports=F(X);var p="__componentSourceLoc",C="$componentSourceLoc",E="react-code-locator.jsxSourceRegistry";function R(t){return t.replace(/\\/g,"/")}function _(t){return t.replace(/\/+$/,"")}function L(t){return R(t).split("/").filter(Boolean)}function K(t,e){let n=L(t),o=L(e),c=0;for(;c<n.length&&c<o.length&&n[c]===o[c];)c+=1;let s=new Array(Math.max(0,n.length-c)).fill(".."),l=o.slice(c),u=[...s,...l];return u.length>0?u.join("/"):"."}function $(t){return t?_(R(t)):""}function g(t){return t?t.match(/^(.*):\d+:\d+$/)?.[1]??null:null}function j(t,e){if(!t)return!1;let n=$(e),o=R(t);return n?o.startsWith(`${n}/`)||o===n?!0:!K(n,o).startsWith("../"):!o.startsWith("../")&&!o.startsWith("/")&&!/^[A-Za-z]:\//.test(o)}function f(t,e){let n=g(t);return j(n??void 0,e)}function D(t,e){return e==="none"?!0:e==="alt"?t.altKey:e==="meta"?t.metaKey:e==="ctrl"?t.ctrlKey:t.shiftKey}function N(t){return Object.keys(t).find(e=>e.startsWith("__reactFiber$")||e.startsWith("__reactInternalInstance$"))}function J(t){let e=t;for(;e;){let n=N(e);if(n)return e[n];e=e.parentElement}return null}function y(t){if(!t)return null;if(typeof t=="function"){let o=t[p];return typeof o=="string"?o:null}if(typeof t!="object")return null;let e=t,n=e[p]??e.type?.[p]??e.render?.[p];return typeof n=="string"?n:null}function w(t){if(t&&typeof t=="object"){let n=globalThis[Symbol.for(E)];if(n instanceof WeakMap){let o=n.get(t);if(typeof o=="string")return o}}let e=t?.[C];return typeof e=="string"?e:null}function z(t){let e=t;for(;e;){let n=y(e.type)??y(e.elementType);if(n)return n;e=e.return??null}return null}function W(t){let e=t?._debugSource;return e?.fileName&&typeof e.lineNumber=="number"?`${e.fileName.replace(/\\/g,"/")}:${e.lineNumber}:${e.columnNumber??1}`:null}function I(t){let e=t,n=[],o=[];for(;e;){let i=w(e.pendingProps)??w(e.memoizedProps)??W(e);if(i){let d=g(i);d&&!n.some(b=>b.source===i)&&n.push({source:i,file:d})}let a=y(e.type)??y(e.elementType);if(a){let d=g(a);d&&!o.some(b=>b.source===a)&&o.push({source:a,file:d})}e=e.return??null}let c=n[0]?.source??null,s=o.find(i=>f(i.source))?.file,l=null;if(s){let i=n.find(a=>a.file===s);if(i)l=i.source;else{let a=o.find(d=>d.file===s);a&&(l=a.source)}}let u=o.find(i=>!f(i.source))?.source??null,r=n.find(i=>!f(i.source))?.source??null,m=n.find(i=>f(i.source))?.source??null,S=l??m??o.find(i=>f(i.source))?.source??null;return{direct:c??S,screen:S,implementation:u??r??S}}function U(t){return t==="direct"?"Direct JSX":t==="screen"?"Screen source":"Implementation source"}function V(t){if(typeof document>"u")return null;let e=document.createElement("div"),n=null,o="screen",c=null;e.setAttribute("data-react-code-locator","true"),Object.assign(e.style,{position:"fixed",right:"12px",bottom:"12px",zIndex:"2147483647",padding:"8px 10px",borderRadius:"8px",background:"rgba(17, 24, 39, 0.92)",color:"#fff",fontSize:"12px",lineHeight:"1.4",fontFamily:"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",boxShadow:"0 8px 30px rgba(0, 0, 0, 0.25)",pointerEvents:"auto",cursor:"pointer",maxWidth:"min(70vw, 720px)",wordBreak:"break-all",opacity:"0",transition:"opacity 120ms ease"});let s=(u,r)=>{e.textContent=u,e.style.background=r==="success"?"rgba(6, 95, 70, 0.92)":r==="error"?"rgba(153, 27, 27, 0.94)":"rgba(17, 24, 39, 0.92)",e.style.opacity="1",e.style.pointerEvents="auto",c&&clearTimeout(c),c=setTimeout(()=>{e.style.opacity="0",e.style.pointerEvents="none"},2e3)};e.addEventListener("click",async()=>{if(n)try{await navigator.clipboard.writeText(n),s("[react-code-locator] copied","success")}catch{s("[react-code-locator] copy failed","error")}}),s(`[react-code-locator] enabled (${t}+click, alt+1/2/3 to switch mode)`,"idle");let l=()=>{!e.isConnected&&document.body&&document.body.appendChild(e)};return document.body?l():document.addEventListener("DOMContentLoaded",l,{once:!0}),{setStatus(u,r="idle"){s(u,r)},setCopyValue(u){n=u},setMode(u){o=u,s(`[react-code-locator] ${U(u)}`,"idle")},remove(){c&&clearTimeout(c),e.remove()}}}function x(t,e="screen"){let n=t instanceof Element?t:t instanceof Node?t.parentElement:null,o=J(n);if(!o)return null;let c=I(o),s=c[e]??c.screen??c.direct??c.implementation;if(s)return{source:s,mode:e};let l=z(o);return l?{source:l,mode:e}:null}function v(t={}){if(process.env.NODE_ENV!=="development")return;let e=V(t.triggerKey??"shift"),n="screen",{triggerKey:o="shift",onLocate:c=r=>{console.log(`[react-code-locator] ${r.source}`),e?.setCopyValue(r.source),e?.setStatus(`[react-code-locator] ${r.source}`,"success")},onError:s=r=>{console.error("[react-code-locator]",r);let m=r instanceof Error?r.message:String(r);e?.setCopyValue(null),e?.setStatus(`[react-code-locator] ${m}`,"error")}}=t;console.log("[react-code-locator] enabled",{triggerKey:o});let l=r=>{if(r.altKey){if(r.code==="Digit1"){n="direct",e?.setMode(n),r.preventDefault();return}if(r.code==="Digit2"){n="screen",e?.setMode(n),r.preventDefault();return}r.code==="Digit3"&&(n="implementation",e?.setMode(n),r.preventDefault())}},u=r=>{if(console.log("[react-code-locator] click",{triggerKey:o,shiftKey:r.shiftKey,altKey:r.altKey,ctrlKey:r.ctrlKey,metaKey:r.metaKey,target:r.target}),!D(r,o))return;let m=x(r.target,n);if(!m){s(new Error("No React component source metadata found for clicked element."));return}r.preventDefault(),r.stopPropagation(),c(m)};return document.addEventListener("click",u,!0),document.addEventListener("keydown",l,!0),()=>{document.removeEventListener("click",u,!0),document.removeEventListener("keydown",l,!0),e?.remove()}}0&&(module.exports={enableReactComponentJump,locateComponentSource});
|
|
400
2
|
//# sourceMappingURL=client.cjs.map
|