feat(runtime): résout react/react-dom vers l'instance host pour les layouts plugin
Le runtime SDK expose désormais react/react-dom résolus contre l'instance hébergée par IdeA plutôt qu'une copie embarquée, pour que les layouts plugin en JSX/hooks partagent le même arbre React que l'host. hello-plugin migre son layout d'exemple en .tsx pour illustrer le contrat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
75
examples/hello-plugin/package-lock.json
generated
75
examples/hello-plugin/package-lock.json
generated
@ -9,6 +9,16 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@idea/plugin-sdk": "file:../.."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"../..": {
|
||||
@ -16,12 +26,77 @@
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typescript": "^5.5.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@idea/plugin-sdk": {
|
||||
"resolved": "../..",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.8",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.8"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@idea/plugin-sdk": "file:../.."
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0 || ^19.0.0",
|
||||
"react-dom": "^18.2.0 || ^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import type { PluginLayoutProps } from "@idea/plugin-sdk";
|
||||
|
||||
let hasLoggedFirstLayoutRender = false;
|
||||
|
||||
export function HelloWorldLayout(props: PluginLayoutProps): string {
|
||||
if (!hasLoggedFirstLayoutRender) {
|
||||
hasLoggedFirstLayoutRender = true;
|
||||
console.info("[hello-plugin] layout first render", {
|
||||
projectId: props.projectId,
|
||||
nodeId: props.nodeId,
|
||||
layoutType: props.layoutType,
|
||||
hasState: props.state !== undefined
|
||||
});
|
||||
}
|
||||
|
||||
return "hello-world";
|
||||
}
|
||||
58
examples/hello-plugin/src/core/layout.tsx
Normal file
58
examples/hello-plugin/src/core/layout.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import type { PluginLayoutProps } from "@idea/plugin-sdk";
|
||||
import type { ReactElement } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
let hasLoggedFirstLayoutRender = false;
|
||||
|
||||
export function HelloWorldLayout(props: PluginLayoutProps): ReactElement {
|
||||
const [localClicks, setLocalClicks] = useState(0);
|
||||
const hostState = useMemo(() => {
|
||||
return typeof props.state === "object" && props.state !== null && !Array.isArray(props.state)
|
||||
? (props.state as Record<string, unknown>)
|
||||
: {};
|
||||
}, [props.state]);
|
||||
const persistedClicks =
|
||||
typeof hostState.clicks === "number" && Number.isFinite(hostState.clicks)
|
||||
? hostState.clicks
|
||||
: 0;
|
||||
|
||||
if (!hasLoggedFirstLayoutRender) {
|
||||
hasLoggedFirstLayoutRender = true;
|
||||
console.info("[hello-plugin] layout first render", {
|
||||
projectId: props.projectId,
|
||||
nodeId: props.nodeId,
|
||||
layoutType: props.layoutType,
|
||||
hasState: props.state !== undefined
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<section style={{ display: "grid", gap: 12, fontFamily: "system-ui, sans-serif" }}>
|
||||
<header>
|
||||
<h2 style={{ margin: 0, fontSize: 16 }}>Hello Plugin</h2>
|
||||
<p style={{ margin: "4px 0 0", color: "#667085", fontSize: 13 }}>
|
||||
React layout rendered by IdeA host React.
|
||||
</p>
|
||||
</header>
|
||||
<dl style={{ display: "grid", gap: 4, margin: 0, fontSize: 13 }}>
|
||||
<div>
|
||||
<dt style={{ color: "#667085" }}>Project</dt>
|
||||
<dd style={{ margin: 0 }}>{props.projectId}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt style={{ color: "#667085" }}>Layout</dt>
|
||||
<dd style={{ margin: 0 }}>{props.layoutType}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setLocalClicks((current) => current + 1);
|
||||
props.setState({ ...hostState, clicks: persistedClicks + 1 });
|
||||
}}
|
||||
>
|
||||
Persist click {persistedClicks} / local click {localClicks}
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -15,7 +15,17 @@ export function activate(ctx: ActivateContext): void {
|
||||
const commandDisposable = ctx.commands?.registerCommand(COMMAND_ID, async () => {
|
||||
const commandRunCount = await recordCommandRun(ctx);
|
||||
const feedback = await runHelloCommandTask(ctx);
|
||||
const opened = await ctx.services?.windows.open({
|
||||
layoutType: LAYOUT_TYPE,
|
||||
state: { openedFromCommand: commandRunCount ?? null }
|
||||
});
|
||||
ctx.logger.info("command executed", { commandId: COMMAND_ID, commandRunCount, feedback });
|
||||
if (opened) {
|
||||
ctx.logger.info("layout window opened", {
|
||||
label: opened.label,
|
||||
alreadyOpen: opened.alreadyOpen
|
||||
});
|
||||
}
|
||||
return feedback;
|
||||
});
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx"
|
||||
]
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"declaration": false,
|
||||
"declarationMap": false,
|
||||
"jsx": "react-jsx",
|
||||
"noEmit": true,
|
||||
"rootDir": "../..",
|
||||
"paths": {
|
||||
@ -13,6 +14,7 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"../../src/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user