Skip to content

Commit 0ec2858

Browse files
committed
Support frameworks not using transformIndexHtml (fixes #13) [publish]
1 parent 4c3ad24 commit 0ec2858

File tree

6 files changed

+100
-47
lines changed

6 files changed

+100
-47
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 4.2.0
4+
5+
- Add support for frameworks not using transformIndexHtml hook like React Router 7 (fixes #13)
6+
37
## 4.1.0
48

59
- Add support for React 19.2

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ export default defineConfig({
2222
});
2323
```
2424

25+
### For frameworks not using transformIndexHtml hook (like React Router 7 framework mode)
26+
27+
> [!NOTE]
28+
> If you're unsure, try first without adding this import
29+
30+
Add this somewhere in your entry file (like `src/main.tsx`):
31+
32+
```ts
33+
import "vite-plugin-react-click-to-component/client";
34+
```
35+
2536
## Inspiration
2637

2738
This plugin is a light version of [ericclemmons/click-to-component](https://github.com/ericclemmons/click-to-component) that uses Vite's launch editor middleware to open the source code in your currently running editor. This also benefits from Vite's transformIndexHtml hook so that you don't need to modify your source code.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "vite-plugin-react-click-to-component",
33
"type": "module",
4-
"version": "4.1.0",
4+
"version": "4.2.0",
55
"private": true,
66
"license": "MIT",
77
"scripts": {

playground/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"postinstall": "rm -rf node_modules/vite",
77
"dev": "vite",
8+
"build": "vite build",
89
"lint": "eslint --max-warnings 0"
910
},
1011
"dependencies": {

scripts/bundle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ await Promise.all([
4545
export declare const reactClickToComponent: () => PluginOption;
4646
`,
4747
);
48+
writeFileSync("dist/client.d.ts", "export {};\n");
4849

4950
writeFileSync(
5051
"dist/package.json",
@@ -60,6 +61,7 @@ export declare const reactClickToComponent: () => PluginOption;
6061
type: "module",
6162
exports: {
6263
".": "./index.js",
64+
"./client": { types: "./client.d.ts" },
6365
},
6466
keywords: [
6567
"vite",

src/index.ts

Lines changed: 81 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,89 @@ import { readFileSync } from "node:fs";
22
import { join } from "node:path";
33
import type { PluginOption } from "vite";
44

5-
let root = "";
6-
let base = "";
5+
export const reactClickToComponent = (): PluginOption => {
6+
let root = "";
7+
let base = "";
8+
let isServe = false;
9+
let clientCode: string | undefined;
710

8-
export const reactClickToComponent = (): PluginOption => ({
9-
name: "react-click-to-component",
10-
apply: "serve",
11-
configResolved(config) {
12-
root = config.root;
13-
base = config.base;
14-
},
15-
transformIndexHtml: () => [
16-
{
17-
tag: "script",
18-
attrs: { type: "module" },
19-
children: readFileSync(join(import.meta.dirname, "client.js"), "utf-8")
20-
.replace("__ROOT__", root)
21-
.replace("__BASE__", base),
11+
return {
12+
name: "react-click-to-component",
13+
configResolved(config) {
14+
root = config.root;
15+
base = config.base;
16+
isServe = config.command === "serve";
2217
},
23-
],
24-
transform: {
25-
filter: { id: /jsx-dev-runtime\.js/u },
26-
handler(code) {
27-
if (code.includes("_source")) return; // React <19, no hack needed
28-
// React 19, inject source into _debugInfo
29-
const defineIndex = code.indexOf('"_debugInfo"');
30-
if (defineIndex === -1) return;
31-
const valueIndex = code.indexOf("value: null", defineIndex);
32-
if (valueIndex === -1) return;
33-
let newCode =
34-
code.slice(0, valueIndex)
35-
+ "value: source"
36-
+ code.slice(valueIndex + 11);
37-
if (code.includes("function ReactElement(type, key, self, source,")) {
18+
19+
// Fix React 19 not injecting source in jsxDEV
20+
transform: {
21+
filter: { id: /jsx-dev-runtime\.js/u },
22+
handler(code) {
23+
if (!isServe) return;
24+
if (code.includes("_source")) return; // React <19, no hack needed
25+
26+
// React 19, inject source into _debugInfo
27+
const defineIndex = code.indexOf('"_debugInfo"');
28+
if (defineIndex === -1) return;
29+
const valueIndex = code.indexOf("value: null", defineIndex);
30+
if (valueIndex === -1) return;
31+
let newCode =
32+
code.slice(0, valueIndex)
33+
+ "value: source"
34+
+ code.slice(valueIndex + 11);
35+
if (code.includes("function ReactElement(type, key, self, source,")) {
36+
return newCode;
37+
}
38+
39+
// React 19.2: we need to inject source jsxDEV -> jsxDEVImpl -> ReactElement
40+
newCode = newCode.replaceAll(
41+
/maybeKey,\s*isStaticChildren/gu,
42+
"maybeKey, isStaticChildren, source",
43+
);
44+
newCode = newCode.replaceAll(
45+
/(\w+)?,\s*debugStack,\s*debugTask/gu,
46+
(m, previousArg) => {
47+
if (previousArg === "source") return m;
48+
return m.replace("debugTask", "debugTask, source");
49+
},
50+
);
3851
return newCode;
39-
}
40-
// React 19.2: we need to inject source jsxDEV -> jsxDEVImpl -> ReactElement
41-
newCode = newCode.replaceAll(
42-
/maybeKey,\s*isStaticChildren/gu,
43-
"maybeKey, isStaticChildren, source",
44-
);
45-
newCode = newCode.replaceAll(
46-
/(\w+)?,\s*debugStack,\s*debugTask/gu,
47-
(m, previousArg) => {
48-
if (previousArg === "source") return m;
49-
return m.replace("debugTask", "debugTask, source");
52+
},
53+
},
54+
55+
// Default: Inject client via transformIndexHtml (no source code modification required)
56+
transformIndexHtml() {
57+
if (!isServe) return;
58+
return [
59+
{
60+
tag: "script",
61+
attrs: { type: "module" },
62+
children:
63+
'import "/@id/__x00__vite-plugin-react-click-to-component/client";',
5064
},
51-
);
52-
return newCode;
65+
];
66+
},
67+
68+
// Inject with `import "vite-plugin-react-click-to-component/client"` for SSR frameworks
69+
resolveId: {
70+
order: "pre",
71+
filter: { id: /^vite-plugin-react-click-to-component\/client$/u },
72+
handler(source) {
73+
return "\0" + source;
74+
},
75+
},
76+
load: {
77+
filter: { id: /^\0vite-plugin-react-click-to-component\/client$/u },
78+
handler() {
79+
if (!isServe) return "";
80+
if (!clientCode) {
81+
clientCode = readFileSync(
82+
join(import.meta.dirname, "client.js"),
83+
"utf-8",
84+
);
85+
}
86+
return clientCode.replace("__ROOT__", root).replace("__BASE__", base);
87+
},
5388
},
54-
},
55-
});
89+
};
90+
};

0 commit comments

Comments
 (0)