Skip to content

Commit 24f5632

Browse files
committed
Refactor Vite and Webpack import scripts in templates to improve clarity and consistency. Update README and installation documentation to reflect changes in entry file structure for React Grab integration.
1 parent a6eb16b commit 24f5632

File tree

10 files changed

+265
-338
lines changed

10 files changed

+265
-338
lines changed

README.md

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -103,34 +103,14 @@ export default function Document() {
103103

104104
#### Vite
105105

106-
Add this to your `index.html`:
106+
Add this at the top of your main entry file (e.g., `src/main.tsx`):
107107

108-
```html
109-
<!doctype html>
110-
<html lang="en">
111-
<head>
112-
<script type="module">
113-
if (import.meta.env.DEV) {
114-
import("react-grab");
115-
}
116-
</script>
117-
</head>
118-
<body>
119-
<div id="root"></div>
120-
<script type="module" src="/src/main.tsx"></script>
121-
</body>
122-
</html>
108+
```tsx
109+
if (import.meta.env.DEV) {
110+
import("react-grab");
111+
}
123112
```
124113

125-
> **Note:** If you use Vite in [Express middleware mode](https://vite.dev/guide/ssr#setting-up-the-dev-server) (e.g. Replit starter templates), the inline script can cause `Failed to parse JSON file` errors when Chromium DevTools is open. For these setups, import from your entry file instead:
126-
>
127-
> ```tsx
128-
> // src/main.tsx
129-
> if (import.meta.env.DEV) {
130-
> import("react-grab");
131-
> }
132-
> ```
133-
134114
#### Webpack
135115

136116
First, install React Grab:

packages/cli/src/utils/templates.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,17 @@ export const NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT = (
9494
)}`;
9595
};
9696

97-
export const VITE_SCRIPT = `<script type="module">
98-
if (import.meta.env.DEV) {
99-
import("react-grab");
100-
}
101-
</script>`;
102-
103-
export const VITE_SCRIPT_WITH_AGENT = (agent: AgentIntegration): string => {
104-
if (agent === "none") return VITE_SCRIPT;
105-
106-
return `<script type="module">
107-
if (import.meta.env.DEV) {
108-
import("react-grab");
109-
import("@react-grab/${agent}/client");
110-
}
111-
</script>`;
97+
export const VITE_IMPORT = `if (import.meta.env.DEV) {
98+
import("react-grab");
99+
}`;
100+
101+
export const VITE_IMPORT_WITH_AGENT = (agent: AgentIntegration): string => {
102+
if (agent === "none") return VITE_IMPORT;
103+
104+
return `if (import.meta.env.DEV) {
105+
import("react-grab");
106+
import("@react-grab/${agent}/client");
107+
}`;
112108
};
113109

114110
export const WEBPACK_IMPORT = `if (process.env.NODE_ENV === "development") {

packages/cli/src/utils/transform.ts

Lines changed: 54 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
NEXT_PAGES_ROUTER_SCRIPT_WITH_AGENT,
1313
SCRIPT_IMPORT,
1414
TANSTACK_EFFECT_WITH_AGENT,
15-
VITE_SCRIPT_WITH_AGENT,
15+
VITE_IMPORT_WITH_AGENT,
1616
WEBPACK_IMPORT_WITH_AGENT,
1717
type AgentIntegration,
1818
} from "./templates.js";
@@ -244,59 +244,7 @@ const addAgentToExistingNextApp = (
244244
};
245245
};
246246

247-
const addAgentToExistingVite = (
248-
originalContent: string,
249-
agent: AgentIntegration,
250-
filePath: string,
251-
): TransformResult => {
252-
if (agent === "none") {
253-
return {
254-
success: true,
255-
filePath,
256-
message: "React Grab is already configured",
257-
noChanges: true,
258-
};
259-
}
260-
261-
const agentPackage = `@react-grab/${agent}`;
262-
if (originalContent.includes(agentPackage)) {
263-
return {
264-
success: true,
265-
filePath,
266-
message: `Agent ${agent} is already configured`,
267-
noChanges: true,
268-
};
269-
}
270-
271-
const agentImport = `import("${agentPackage}/client");`;
272-
const reactGrabImportMatch = originalContent.match(
273-
/import\s*\(\s*["']react-grab["']\s*\);?/,
274-
);
275-
276-
if (reactGrabImportMatch) {
277-
const matchedText = reactGrabImportMatch[0];
278-
const hasSemicolon = matchedText.endsWith(";");
279-
const newContent = originalContent.replace(
280-
matchedText,
281-
`${hasSemicolon ? matchedText.slice(0, -1) : matchedText};\n ${agentImport}`,
282-
);
283-
return {
284-
success: true,
285-
filePath,
286-
message: `Add ${agent} agent`,
287-
originalContent,
288-
newContent,
289-
};
290-
}
291-
292-
return {
293-
success: false,
294-
filePath,
295-
message: "Could not find React Grab import to add agent after",
296-
};
297-
};
298-
299-
const addAgentToExistingWebpack = (
247+
const addAgentToExistingImport = (
300248
originalContent: string,
301249
agent: AgentIntegration,
302250
filePath: string,
@@ -567,52 +515,61 @@ const transformNextPagesRouter = (
567515
};
568516
};
569517

518+
const checkExistingInstallation = (
519+
filePath: string,
520+
agent: AgentIntegration,
521+
reactGrabAlreadyConfigured: boolean,
522+
): TransformResult | null => {
523+
const content = readFileSync(filePath, "utf-8");
524+
if (!hasReactGrabCode(content)) return null;
525+
526+
if (reactGrabAlreadyConfigured) {
527+
return addAgentToExistingImport(content, agent, filePath);
528+
}
529+
return {
530+
success: true,
531+
filePath,
532+
message: "React Grab is already installed in this file",
533+
noChanges: true,
534+
};
535+
};
536+
570537
const transformVite = (
571538
projectRoot: string,
572539
agent: AgentIntegration,
573540
reactGrabAlreadyConfigured: boolean,
574541
force: boolean = false,
575542
): TransformResult => {
576-
const indexPath = findIndexHtml(projectRoot);
543+
const entryPath = findEntryFile(projectRoot);
544+
545+
if (!force) {
546+
const indexPath = findIndexHtml(projectRoot);
547+
if (indexPath) {
548+
const existingResult = checkExistingInstallation(indexPath, agent, reactGrabAlreadyConfigured);
549+
if (existingResult) return existingResult;
550+
}
551+
}
577552

578-
if (!indexPath) {
553+
if (!entryPath) {
579554
return {
580555
success: false,
581556
filePath: "",
582-
message: "Could not find index.html",
557+
message: "Could not find entry file (src/index.tsx, src/main.tsx, etc.)",
583558
};
584559
}
585560

586-
const originalContent = readFileSync(indexPath, "utf-8");
587-
let newContent = originalContent;
588-
const hasReactGrabInFile = hasReactGrabCode(originalContent);
589-
590-
if (!force && hasReactGrabInFile && reactGrabAlreadyConfigured) {
591-
return addAgentToExistingVite(originalContent, agent, indexPath);
592-
}
593-
594-
if (!force && hasReactGrabInFile) {
595-
return {
596-
success: true,
597-
filePath: indexPath,
598-
message: "React Grab is already installed in this file",
599-
noChanges: true,
600-
};
561+
if (!force) {
562+
const existingResult = checkExistingInstallation(entryPath, agent, reactGrabAlreadyConfigured);
563+
if (existingResult) return existingResult;
601564
}
602565

603-
const scriptBlock = VITE_SCRIPT_WITH_AGENT(agent);
604-
605-
const headMatch = newContent.match(/<head[^>]*>/i);
606-
if (headMatch) {
607-
newContent = newContent.replace(
608-
headMatch[0],
609-
`${headMatch[0]}\n ${scriptBlock}`,
610-
);
611-
}
566+
const originalContent = readFileSync(entryPath, "utf-8");
567+
const importBlock = VITE_IMPORT_WITH_AGENT(agent);
568+
const newContent = `${importBlock}\n\n${originalContent}`;
612569

613570
return {
614571
success: true,
615-
filePath: indexPath,
572+
filePath: entryPath,
616573
message:
617574
"Add React Grab" + (agent !== "none" ? ` with ${agent} agent` : ""),
618575
originalContent,
@@ -636,22 +593,12 @@ const transformWebpack = (
636593
};
637594
}
638595

639-
const originalContent = readFileSync(entryPath, "utf-8");
640-
const hasReactGrabInFile = hasReactGrabCode(originalContent);
641-
642-
if (!force && hasReactGrabInFile && reactGrabAlreadyConfigured) {
643-
return addAgentToExistingWebpack(originalContent, agent, entryPath);
644-
}
645-
646-
if (!force && hasReactGrabInFile) {
647-
return {
648-
success: true,
649-
filePath: entryPath,
650-
message: "React Grab is already installed in this file",
651-
noChanges: true,
652-
};
596+
if (!force) {
597+
const existingResult = checkExistingInstallation(entryPath, agent, reactGrabAlreadyConfigured);
598+
if (existingResult) return existingResult;
653599
}
654600

601+
const originalContent = readFileSync(entryPath, "utf-8");
655602
const importBlock = WEBPACK_IMPORT_WITH_AGENT(agent);
656603
const newContent = `${importBlock}\n\n${originalContent}`;
657604

@@ -1128,8 +1075,17 @@ const findReactGrabFile = (
11281075
return findLayoutFile(projectRoot);
11291076
}
11301077
return findDocumentFile(projectRoot);
1131-
case "vite":
1132-
return findIndexHtml(projectRoot);
1078+
case "vite": {
1079+
const entryFile = findEntryFile(projectRoot);
1080+
if (entryFile && hasReactGrabCode(readFileSync(entryFile, "utf-8"))) {
1081+
return entryFile;
1082+
}
1083+
const indexHtml = findIndexHtml(projectRoot);
1084+
if (indexHtml && hasReactGrabCode(readFileSync(indexHtml, "utf-8"))) {
1085+
return indexHtml;
1086+
}
1087+
return entryFile;
1088+
}
11331089
case "tanstack":
11341090
return findTanStackRootFile(projectRoot);
11351091
case "webpack":

packages/cli/test/configure.test.ts

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -333,26 +333,18 @@ export default function Document() {
333333
});
334334

335335
describe("previewOptionsTransform - Vite", () => {
336-
const indexWithReactGrab = `<!doctype html>
337-
<html lang="en">
338-
<head>
339-
<script type="module">
340-
if (import.meta.env.DEV) {
341-
import("react-grab");
342-
}
343-
</script>
344-
</head>
345-
<body>
346-
<div id="root"></div>
347-
<script type="module" src="/src/main.tsx"></script>
348-
</body>
349-
</html>`;
336+
const entryWithReactGrab = `if (import.meta.env.DEV) {
337+
import("react-grab");
338+
}
339+
340+
import React from "react";
341+
import ReactDOM from "react-dom/client";`;
350342

351343
it("should add options to Vite import", () => {
352344
mockExistsSync.mockImplementation((path) =>
353-
String(path).endsWith("index.html"),
345+
String(path).endsWith("main.tsx"),
354346
);
355-
mockReadFileSync.mockReturnValue(indexWithReactGrab);
347+
mockReadFileSync.mockReturnValue(entryWithReactGrab);
356348

357349
const options: ReactGrabOptions = {
358350
activationKey: "Space",
@@ -366,24 +358,17 @@ describe("previewOptionsTransform - Vite", () => {
366358
});
367359

368360
it("should update existing options in Vite import without duplicating", () => {
369-
const indexWithExistingOptions = `<!doctype html>
370-
<html lang="en">
371-
<head>
372-
<script type="module">
373-
if (import.meta.env.DEV) {
374-
import("react-grab").then((m) => m.init({"activationKey":"g"}));
375-
}
376-
</script>
377-
</head>
378-
<body>
379-
<div id="root"></div>
380-
</body>
381-
</html>`;
361+
const entryWithExistingOptions = `if (import.meta.env.DEV) {
362+
import("react-grab").then((m) => m.init({"activationKey":"g"}));
363+
}
364+
365+
import React from "react";
366+
import ReactDOM from "react-dom/client";`;
382367

383368
mockExistsSync.mockImplementation((path) =>
384-
String(path).endsWith("index.html"),
369+
String(path).endsWith("main.tsx"),
385370
);
386-
mockReadFileSync.mockReturnValue(indexWithExistingOptions);
371+
mockReadFileSync.mockReturnValue(entryWithExistingOptions);
387372

388373
const options: ReactGrabOptions = {
389374
activationKey: "Meta+K",
@@ -400,9 +385,9 @@ describe("previewOptionsTransform - Vite", () => {
400385

401386
it("should add multiple options to Vite import", () => {
402387
mockExistsSync.mockImplementation((path) =>
403-
String(path).endsWith("index.html"),
388+
String(path).endsWith("main.tsx"),
404389
);
405-
mockReadFileSync.mockReturnValue(indexWithReactGrab);
390+
mockReadFileSync.mockReturnValue(entryWithReactGrab);
406391

407392
const options: ReactGrabOptions = {
408393
activationKey: "Alt+E",
@@ -420,18 +405,13 @@ describe("previewOptionsTransform - Vite", () => {
420405
});
421406

422407
it("should fail when React Grab import not found", () => {
423-
const indexWithoutReactGrab = `<!doctype html>
424-
<html lang="en">
425-
<head></head>
426-
<body>
427-
<div id="root"></div>
428-
</body>
429-
</html>`;
408+
const entryWithoutReactGrab = `import React from "react";
409+
import ReactDOM from "react-dom/client";`;
430410

431411
mockExistsSync.mockImplementation((path) =>
432-
String(path).endsWith("index.html"),
412+
String(path).endsWith("main.tsx"),
433413
);
434-
mockReadFileSync.mockReturnValue(indexWithoutReactGrab);
414+
mockReadFileSync.mockReturnValue(entryWithoutReactGrab);
435415

436416
const options: ReactGrabOptions = {
437417
activationKey: "Space",

0 commit comments

Comments
 (0)