Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/content-main-world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let inspecting = false;
let openInEditorUrl = DEFAULT_OPEN_IN_EDITOR_URL;
const mousePos = { x: 0, y: 0 };
let openInEditorMethod = 'url';
let pathMappings = '';

const getInspectName = (element: HTMLElement) => {
const fiber = findFiberByHostInstance(element);
Expand Down Expand Up @@ -74,7 +75,7 @@ const handleInspectorClick = async (e: MouseEvent) => {
target.id = tmpId;
window.postMessage("inspected", "*");

const deepLink = getEditorLink(openInEditorUrl, fiber._debugSource)
const deepLink = getEditorLink(openInEditorUrl, fiber._debugSource, pathMappings)
if(openInEditorMethod === 'fetch'){
fetch(deepLink);
}else{
Expand All @@ -101,6 +102,7 @@ window.addEventListener("message", ({ data }) => {
if (data.type === "options" && data.openInEditorUrl) {
openInEditorUrl = data.openInEditorUrl;
openInEditorMethod = data.openInEditorMethod;
pathMappings = data.pathMappings || '';
}
});

Expand Down
2 changes: 1 addition & 1 deletion src/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ script.onload = () => {
if (request === "inspect") {
window.postMessage(request, "*");
chrome.storage.sync.get(
{ openInEditorUrl: DEFAULT_OPEN_IN_EDITOR_URL, openInEditorMethod:'url' },
{ openInEditorUrl: DEFAULT_OPEN_IN_EDITOR_URL, openInEditorMethod:'url', pathMappings: '' },
(items) => {
window.postMessage({ type: "options", ...items }, "*");
}
Expand Down
9 changes: 9 additions & 0 deletions src/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ <h1>React Inspector Options</h1>
<option value="fetch">Call with fetch</option>
</select>
</label>
<label style="margin-top: 15px;">
Path Mappings (for Docker/remote paths):
<textarea
id="path-mappings"
placeholder="/home/node/frontend|/Users/you/project"
style="width: 100%; height: 80px; padding: 7px; border: 1px solid #afafaf; border-radius: 4px; font-family: monospace; resize: vertical;"
></textarea>
<small style="display: block; margin-top: 5px; color: #666;">Format: /container/path|/local/path (one per line)</small>
</label>
<button class="primary" id="save" style="margin-top:1rem">Save</button>
<div id="status"></div>
</div>
Expand Down
18 changes: 12 additions & 6 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,32 @@ const getElements = () => {

const openInEditorMethod = document.getElementById('open-in-editor-method') as HTMLSelectElement;

return {openInEditorUrl,openInEditorMethod}
const pathMappings = document.getElementById('path-mappings') as HTMLTextAreaElement;

return {openInEditorUrl, openInEditorMethod, pathMappings}
}

const initOptions = () => {

const { openInEditorUrl, openInEditorMethod } = getElements();
const { openInEditorUrl, openInEditorMethod, pathMappings } = getElements();

chrome.storage.sync.get(
{ openInEditorUrl: DEFAULT_OPEN_IN_EDITOR_URL, openInEditorMethod:'url' },
{ openInEditorUrl: DEFAULT_OPEN_IN_EDITOR_URL, openInEditorMethod:'url', pathMappings: '' },
(items) => {
openInEditorUrl.value = items.openInEditorUrl;
openInEditorMethod.value = items.openInEditorMethod;
pathMappings.value = items.pathMappings;
}
);
};

const saveOptions = (feedbackMsg: string) => {
const { openInEditorUrl, openInEditorMethod } = getElements();
const { openInEditorUrl, openInEditorMethod, pathMappings } = getElements();

chrome.storage.sync.set({ openInEditorUrl: openInEditorUrl.value, openInEditorMethod:openInEditorMethod.value }, () => {
chrome.storage.sync.set({
openInEditorUrl: openInEditorUrl.value,
openInEditorMethod: openInEditorMethod.value,
pathMappings: pathMappings.value
}, () => {
const status = document.getElementById("status")!;
status.textContent = feedbackMsg;
setTimeout(() => {
Expand Down
48 changes: 34 additions & 14 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,54 @@ export const checkDevtoolsGlobalHook = (): boolean =>
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ &&
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers &&
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0 &&
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.get(1);

// TODO Refactoring needed ref react/packages/react-devtools-shared/src/backend/agent.js getBestMatchingRendererInterface
const getDevtoolsGlobalHookRenderer = () => {
if (!checkDevtoolsGlobalHook()) return null;
return window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.get(1);
};
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.values().length > 0;

export const findFiberByHostInstance = (
target: HTMLElement
): { _debugSource: DebugSource } | null => {
if (!checkDevtoolsGlobalHook()) return null;
const renderers = window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers;

// Try all available renderers
for (const [, renderer] of renderers.entries()) {
if (!renderer?.findFiberByHostInstance) continue;

const fiber = renderer.findFiberByHostInstance(target);
if (fiber && fiber._debugSource) {
return fiber;
}
}

return null;
};

const renderer = getDevtoolsGlobalHookRenderer();
if (!renderer) return null;
export const applyPathMappings = (path: string, pathMappings: string): string => {
if (!path || !pathMappings) return path;

const fiber = renderer.findFiberByHostInstance(target) || null;
const lines = pathMappings.split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;

return fiber && fiber._debugSource ? fiber : null;
const [from, to] = trimmed.split('|');
if (from && to && path.startsWith(from.trim())) {
return path.replace(from.trim(), to.trim());
}
}

return path;
};

export const getEditorLink = (
openInEditorUrl: string,
debugSource: DebugSource
debugSource: DebugSource,
pathMappings: string = ''
) => {
const { fileName, columnNumber, lineNumber } = debugSource;
const { columnNumber, lineNumber } = debugSource;
const fileName = applyPathMappings(debugSource.fileName || '', pathMappings);

return openInEditorUrl
.replace("{path}", fileName || "")
.replace("{path}", fileName)
.replace("{line}", lineNumber ? lineNumber.toString() : "0")
.replace("{column}", columnNumber ? columnNumber.toString() : "0");
};
Expand Down