Skip to content

Commit

Permalink
Merge branch 'main' into @p-malecki/fix-zoom-lvl-selection
Browse files Browse the repository at this point in the history
  • Loading branch information
p-malecki committed Nov 22, 2024
2 parents 88ea55a + 11206ca commit 11d3081
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 74 deletions.
8 changes: 8 additions & 0 deletions packages/docs/docs/guides/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ Native builds are one of the most time consuming phases when launching your proj
Build processes output a lot of logs on their own and hence they have separate output channels.
When something goes wrong in the native build phase, instead of checking "Radon IDE" source in "Output Panel" as described in the previous point, select "Radon IDE (Android build)" or "Radon IDE (iOS build)" source depending on the platform you're building for.

### -sec-num Accessing application process logs

In cases of native crashes on iOS or Android, it may be helpful to investigate those by checking iOS process output, or Android logcat.
When the application is launched, Radon IDE creates a separate output channel to record logs printed by the application process.
In order to see it, you can go to "Output" panel and select "Radon IDE (iOS Simulator Logs)" for logs from your iOS application process or "Radon IDE (Android Emulator Logs)" to see android's logcat entries associated with your app.

Note: iOS Simulator Logs currently doesn't work on Expo Go and Expo Dev Client projects.

### -sec-num- Fresh installation in VSCode / Cursor

There are two locations on the disk where Radon IDE stores its information.
Expand Down
180 changes: 118 additions & 62 deletions packages/vscode-extension/lib/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ const { useContext, useState, useEffect, useRef, useCallback } = require("react"
const {
LogBox,
AppRegistry,
Dimensions,
RootTagContext,
View,
Dimensions,
Linking,
findNodeHandle,
} = require("react-native");
const { storybookPreview } = require("./storybook_helper");

// https://github.com/facebook/react/blob/c3570b158d087eb4e3ee5748c4bd9360045c8a26/packages/react-reconciler/src/ReactWorkTags.js#L62
const OffscreenComponentReactTag = 22;

const navigationPlugins = [];
export function registerNavigationPlugin(name, plugin) {
navigationPlugins.push({ name, plugin });
Expand Down Expand Up @@ -69,6 +72,113 @@ function useAgentListener(agent, eventName, listener, deps = []) {
}, [agent, ...deps]);
}

function getRendererConfig() {
const renderers = Array.from(window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.values());
if (!renderers) {
return undefined;
}
for (const renderer of renderers) {
if (renderer.rendererConfig?.getInspectorDataForInstance) {
return renderer.rendererConfig;
}
}
return undefined;
}

/**
* Return an array of component data representing a stack of components by traversing
* the component hierarchy up from the startNode.
* Each stack entry carries the component name, source location and measure function.
*/
function extractComponentStack(startNode) {
const rendererConfig = getRendererConfig();

if (!rendererConfig) {
console.warn("Unable to find functional renderer config.");
return [];
}

const componentStack = [];
let node = startNode;

// Optimization: we break after reaching fiber node corresponding to OffscreenComponent
while (node && node.tag !== OffscreenComponentReactTag) {
const data = rendererConfig.getInspectorDataForInstance(node);

const item = data.hierarchy[data.hierarchy.length - 1];
const inspectorData = item.getInspectorData(findNodeHandle);
if (inspectorData.source) {
componentStack.push({
measure: inspectorData.measure,
name: item.name,
source: inspectorData.source,
});
}

node = node.return;
}
return componentStack;
}

function getInspectorDataForCoordinates(mainContainerRef, x, y, requestStack, callback) {
const { width: screenWidth, height: screenHeight } = Dimensions.get("screen");

RNInternals.getInspectorDataForViewAtPoint(
mainContainerRef.current,
x * screenWidth,
y * screenHeight,
(viewData) => {
const frame = viewData.frame;
const scaledFrame = {
x: frame.left / screenWidth,
y: frame.top / screenHeight,
width: frame.width / screenWidth,
height: frame.height / screenHeight,
};

if (!requestStack) {
callback({ frame: scaledFrame });
return;
}

const inspectorDataStack = extractComponentStack(viewData.closestInstance);
Promise.all(
inspectorDataStack.map(
(inspectorData) =>
new Promise((res, rej) => {
try {
inspectorData.measure((_x, _y, viewWidth, viewHeight, pageX, pageY) => {
const source = inspectorData.source;
res({
componentName: inspectorData.name,
source: {
fileName: source.fileName,
line0Based: source.lineNumber - 1,
column0Based: source.columnNumber - 1,
},
frame: {
x: pageX / screenWidth,
y: pageY / screenHeight,
width: viewWidth / screenWidth,
height: viewHeight / screenHeight,
},
});
});
} catch (e) {
rej(e);
}
})
)
).then((componentDataStack) => {
callback({
frame: scaledFrame,
stack: componentDataStack,
});
});
}
);
}

export function AppWrapper({ children, initialProps, fabric }) {
const rootTag = useContext(RootTagContext);
const [devtoolsAgent, setDevtoolsAgent] = useState(null);
Expand Down Expand Up @@ -180,68 +290,14 @@ export function AppWrapper({ children, initialProps, fabric }) {
devtoolsAgent,
"RNIDE_inspect",
(payload) => {
const getInspectorDataForViewAtPoint = RNInternals.getInspectorDataForViewAtPoint;
const { width, height } = Dimensions.get("screen");

getInspectorDataForViewAtPoint(
mainContainerRef.current,
payload.x * width,
payload.y * height,
(viewData) => {
const frame = viewData.frame;
const scaledFrame = {
x: frame.left / width,
y: frame.top / height,
width: frame.width / width,
height: frame.height / height,
};
let stackPromise = Promise.resolve(undefined);
if (payload.requestStack) {
stackPromise = Promise.all(
viewData.hierarchy.reverse().map((item) => {
const inspectorData = item.getInspectorData((arg) => findNodeHandle(arg));
const framePromise = new Promise((resolve, reject) => {
try {
inspectorData.measure((_x, _y, viewWidth, viewHeight, pageX, pageY) => {
resolve({
x: pageX / width,
y: pageY / height,
width: viewWidth / width,
height: viewHeight / height,
});
});
} catch (e) {
reject(e);
}
});
const { id, x, y, requestStack } = payload;

return framePromise
.catch(() => undefined)
.then((frame) => {
return inspectorData.source
? {
componentName: item.name,
source: {
fileName: inspectorData.source.fileName,
line0Based: inspectorData.source.lineNumber - 1,
column0Based: inspectorData.source.columnNumber - 1,
},
frame,
}
: undefined;
});
})
).then((stack) => stack?.filter(Boolean));
}
stackPromise.then((stack) => {
devtoolsAgent._bridge.send("RNIDE_inspectData", {
id: payload.id,
frame: scaledFrame,
stack: stack,
});
});
}
);
getInspectorDataForCoordinates(mainContainerRef, x, y, requestStack, (inspectorData) => {
devtoolsAgent._bridge.send("RNIDE_inspectData", {
id,
...inspectorData,
});
});
},
[mainContainerRef]
);
Expand Down
12 changes: 11 additions & 1 deletion packages/vscode-extension/src/builders/eas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ async function fetchBuild(config: EasConfig, platform: DevicePlatform) {

const build = maxBy(builds, "completedAt")!;

if (!build.binaryUrl.endsWith(".apk") && !build.binaryUrl.endsWith(".apex")) {
Logger.error(
`EAS build artifact needs to be a development build in .apk or .apex format to work with the Radon IDE, make sure you set up eas to use "development" profile`
);
return undefined;
}

Logger.debug(`Using EAS build artifact with ID ${build.id}.`);
return build;
}
Expand All @@ -80,7 +87,10 @@ async function downloadAppFromEas(
const { id, binaryUrl } = build;

const tmpDirectory = await mkdtemp(path.join(os.tmpdir(), "rn-ide-eas-build-"));
const binaryPath = path.join(tmpDirectory, id);
const binaryPath =
platform === DevicePlatform.Android
? path.join(tmpDirectory, `${id}.apk`)
: path.join(tmpDirectory, id);

const success = await downloadBinary(binaryUrl, binaryPath);
if (!success) {
Expand Down
30 changes: 24 additions & 6 deletions packages/vscode-extension/src/debugging/DebugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class DebugAdapter extends DebugSession {
private sourceMapAliases?: Array<[string, string]>;
private threads: Array<Thread> = [];
private sourceMaps: Array<[string, string, SourceMapConsumer, number]> = [];
private sourceMapFilePaths: Set<string> = new Set();
private expoPreludeLineCount: number;
private linesStartAt1 = true;
private columnsStartAt1 = true;
Expand Down Expand Up @@ -172,6 +173,10 @@ export class DebugAdapter extends DebugSession {
lineOffset = this.expoPreludeLineCount;
}

// add all sources from consumer to sourceMapFilePaths
consumer.sources.forEach((source) => {
this.sourceMapFilePaths.add(source);
});
this.sourceMaps.push([
message.params.url,
message.params.scriptId,
Expand All @@ -194,6 +199,7 @@ export class DebugAdapter extends DebugSession {
const allThreads = this.threads;
this.threads = [];
this.sourceMaps = [];
this.sourceMapFilePaths.clear();
this.variableStore.clearReplVariables();
this.variableStore.clearCDPVariables();

Expand Down Expand Up @@ -299,7 +305,7 @@ export class DebugAdapter extends DebugSession {
]);
}

private toAbsoluteFilePath(sourceMapPath: string) {
private toAbsoluteFilePathFromSourceMapAlias(sourceMapPath: string) {
if (this.sourceMapAliases) {
for (const [alias, absoluteFilePath] of this.sourceMapAliases) {
if (sourceMapPath.startsWith(alias)) {
Expand All @@ -311,7 +317,7 @@ export class DebugAdapter extends DebugSession {
return sourceMapPath;
}

private toSourceMapFilePath(sourceAbsoluteFilePath: string) {
private toSourceMapAliasedFilePath(sourceAbsoluteFilePath: string) {
if (this.sourceMapAliases) {
// we return the first alias from the list
for (const [alias, absoluteFilePath] of this.sourceMapAliases) {
Expand Down Expand Up @@ -358,7 +364,7 @@ export class DebugAdapter extends DebugSession {
});

return {
sourceURL: this.toAbsoluteFilePath(sourceURL),
sourceURL: this.toAbsoluteFilePathFromSourceMapAlias(sourceURL),
lineNumber1Based: sourceLine1Based,
columnNumber0Based: sourceColumn0Based,
scriptURL,
Expand Down Expand Up @@ -495,8 +501,20 @@ export class DebugAdapter extends DebugSession {
this.sendResponse(response);
}

private toGeneratedPosition(file: string, lineNumber1Based: number, columnNumber0Based: number) {
let sourceMapFilePath = this.toSourceMapFilePath(file);
private toGeneratedPosition(
absoluteFilePath: string,
lineNumber1Based: number,
columnNumber0Based: number
) {
// New React Native 76 debugger uses file aliases in source maps, however, the aliases are not
// used in some settings (i.e. with Expo projects). For calculating the generated position, we
// need to use the file path that is present in source maps. We first try to check if the aliased
// file path is there, and if it's not, we use the original absolute file path.
let sourceMapAliasedFilePath = this.toSourceMapAliasedFilePath(absoluteFilePath);
let sourceMapFilePath = this.sourceMapFilePaths.has(sourceMapAliasedFilePath)
? sourceMapAliasedFilePath
: absoluteFilePath;

let position: NullablePosition = { line: null, column: null, lastColumn: null };
let originalSourceURL: string = "";
this.sourceMaps.forEach(([sourceURL, scriptId, consumer, lineOffset]) => {
Expand Down Expand Up @@ -551,7 +569,7 @@ export class DebugAdapter extends DebugSession {
consumer.eachMapping((mapping) => mapping.source && uniqueSourceMapPaths.add(mapping.source));

uniqueSourceMapPaths.forEach((sourceMapPath) => {
const absoluteFilePath = this.toAbsoluteFilePath(sourceMapPath);
const absoluteFilePath = this.toAbsoluteFilePathFromSourceMapAlias(sourceMapPath);
const breakpoints = this.breakpoints.get(absoluteFilePath) || [];
breakpoints.forEach(async (bp) => {
if (bp.verified) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class LaunchConfigController implements Disposable, LaunchConfig {
return {};
}

const { android, appRoot, ios, isExpo, metroConfigPath, env } = RNIDEConfiguration;
const { android, appRoot, ios, isExpo, metroConfigPath, env, eas } = RNIDEConfiguration;

return { android, appRoot, ios, isExpo, metroConfigPath, env };
return { android, appRoot, ios, isExpo, metroConfigPath, env, eas };
};

this.config = getCurrentConfig();
Expand Down
9 changes: 9 additions & 0 deletions packages/vscode-extension/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@ export class Project

public async reload(type: ReloadAction): Promise<boolean> {
this.updateProjectState({ status: "starting" });

// this action needs to be handled outside of device session as it resets the device session itself
if (type === "reboot") {
const deviceInfo = this.projectState.selectedDevice!;
await this.start(true, false);
await this.selectDevice(deviceInfo);
return true;
}

const success = (await this.deviceSession?.perform(type)) ?? false;
if (success) {
this.updateProjectState({ status: "running" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function ReloadButton({ disabled }: { disabled: boolean }) {
"Restart app process": () => project.reload("restartProcess"),
"Reinstall app": () => project.reload("reinstall"),
"Clear Metro cache": () => project.restart("metro"),
"Reboot IDE": () => project.reload("reboot"),
"Clean rebuild": () => project.restart("all"),
}}>
<span className="codicon codicon-refresh" />
Expand Down
Loading

0 comments on commit 11d3081

Please sign in to comment.