Skip to content
This repository has been archived by the owner on Oct 9, 2024. It is now read-only.

Commit

Permalink
docs: Add dependency graph viewer to live code preview
Browse files Browse the repository at this point in the history
  • Loading branch information
mohebifar committed Mar 9, 2024
1 parent 829a728 commit 89be8a2
Show file tree
Hide file tree
Showing 20 changed files with 538 additions and 88 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-snails-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-unforget/babel-plugin": patch
---

Expose mermaid dependency graph generator API
4 changes: 2 additions & 2 deletions apps/docs/components/CodeEditorAndPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
useSandpack,
} from "@codesandbox/sandpack-react";
import { memo, useCallback, useEffect, useRef } from "react";
import { twMerge } from "tailwind-merge";
import { cn } from "utils/cn";

type CodeEditorAndPreviewProps = {
readOnly?: boolean;
Expand Down Expand Up @@ -98,7 +98,7 @@ export const CodeEditorAndPreview = memo(
<div className="h-3 bg-gray-100" />
<SandpackPreview
ref={sandboxPreview}
className={twMerge(["bg-base-200 h-46", previewClassName])}
className={cn(["bg-base-200 h-46", previewClassName])}
/>
</div>
);
Expand Down
73 changes: 73 additions & 0 deletions apps/docs/components/DependencyGraphViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"use client";

import { useEffect, useRef, useState } from "react";

interface DependencyGraphViewerProps {
mermaidSyntax: string;
}

function createNodeFromHTML(svgString: string) {
var div = document.createElement("div");
div.innerHTML = svgString.trim();

return div.firstElementChild as SVGElement;
}

type MermaidDefault = (typeof import("mermaid"))["default"];
type SvgPanZoomDefault = typeof import("svg-pan-zoom");

const DependencyGraphViewer = ({
mermaidSyntax,
}: DependencyGraphViewerProps) => {
const container = useRef<HTMLDivElement>(null);

const [modules, setModules] = useState<
[MermaidDefault, SvgPanZoomDefault] | null
>(null);

useEffect(() => {
Promise.all([import("mermaid"), import("svg-pan-zoom")]).then(
([mermaidModule, svgPanZoomModule]) => {
mermaidModule.default.initialize({
startOnLoad: true,
theme: "dark",
});

setModules([mermaidModule.default, svgPanZoomModule.default]);
},
);
}, []);

useEffect(() => {
if (mermaidSyntax && container.current && modules) {
const [mermaid, svgPanZoom] = modules;
(async () => {
const { svg } = await mermaid.render("mermaidGraph", mermaidSyntax);

while (container.current.hasChildNodes()) {
container.current.removeChild(container.current.lastChild);
}

const svgNode = createNodeFromHTML(svg);
svgNode.setAttribute("height", "100%");
container.current.appendChild(svgNode);

svgPanZoom(svgNode, {
controlIconsEnabled: true,
});
})();
}
}, [mermaidSyntax, modules]);

return (
<div>
<p className="mb-6 text-sm">
Below is a visual representation of the dependency graph that React
Unforget's compiler sees to ultimately optimize your code.
</p>
<div ref={container} className="w-full h-[600px]" />
</div>
);
};

export default DependencyGraphViewer;
14 changes: 14 additions & 0 deletions apps/docs/components/DynamicDependencyGraphViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import dynamic from "next/dynamic";

const DynamicDependencyGraphViewer = dynamic(
() => import("./DependencyGraphViewer"),
{
loading: () => (
<div className="flex items-center justify-center h-[300px]">
<span className="loading loading-spinner loading-lg" />
</div>
),
},
);

export { DynamicDependencyGraphViewer };
4 changes: 2 additions & 2 deletions apps/docs/components/DynamicLiveCodeSandpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ const DynamicLiveCodeSandpack = dynamic(() => import("./LiveCodeSandpack"), {
<BeforeAfterCodeLayout
before={
<div className="h-96 flex items-center justify-center">
<span className="loading loading-spinner loading-md"></span>{" "}
<span className="loading loading-spinner loading-md" />
</div>
}
after={
<div className="h-96 flex items-center justify-center">
<span className="loading loading-spinner loading-md"></span>{" "}
<span className="loading loading-spinner loading-md" />
</div>
}
/>
Expand Down
119 changes: 77 additions & 42 deletions apps/docs/components/LiveCodeSandpack.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { transform } from "@babel/standalone";
import { SandpackProvider } from "@codesandbox/sandpack-react";
import reactUnforgetBabelPlugin from "@react-unforget/babel-plugin";
import reactUnforgetBabelPlugin, {
mermaidGraphFromComponent,
} from "@react-unforget/babel-plugin";

// @ts-ignore
import jsxBabelPlugin from "@babel/plugin-syntax-jsx";
import { useTheme } from "nextra-theme-docs";
import { useCallback, useEffect, useMemo, useState } from "react";
import { BeforeAfterCodeLayout } from "./BeforeAfterCodeLayout";
import { CodeEditorAndPreview } from "./CodeEditorAndPreview";
import { DynamicDependencyGraphViewer } from "./DynamicDependencyGraphViewer";

const indexFileContent = `
import { createRoot } from "react-dom/client";
Expand All @@ -31,19 +34,34 @@ export interface LiveCodeProps {
previewClassName?: string;
}

function transformCode(content: string) {
const result = transform(content, {
plugins: [jsxBabelPlugin, reactUnforgetBabelPlugin],
});

return result?.code ?? "";
}

function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
const [beforeCode, setBeforeCode] = useState(children);
const [afterCode, setAfterCode] = useState(defaultLoadingCode);
const [mermaidSyntax, setMermaidSyntax] = useState<any>(null);
const [viewDependencyGraph, setViewDependencyGraph] = useState(false);
const handleToggleDependencyGraph = useCallback(() => {
setViewDependencyGraph((prev) => !prev);
}, []);
const { theme } = useTheme();

const transformCode = useCallback((content: string) => {
const result = transform(content, {
plugins: [
jsxBabelPlugin,
[
reactUnforgetBabelPlugin,
{
_debug_onComponentAnalysisFinished: (component: any) => {
setMermaidSyntax(mermaidGraphFromComponent(component));
},
},
],
],
});

return result?.code ?? "";
}, []);

const handleCodeChange = useCallback((newCode: string) => {
setBeforeCode(newCode);
}, []);
Expand Down Expand Up @@ -83,39 +101,56 @@ function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
);

return (
<BeforeAfterCodeLayout
before={
<SandpackProvider
template="react"
files={beforeFiles}
theme={theme === "system" || !theme ? "auto" : (theme as any)}
>
<CodeEditorAndPreview
onChange={handleCodeChange}
previewClassName={previewClassName}
/>
</SandpackProvider>
}
after={
<SandpackProvider
customSetup={{
dependencies: {
"@react-unforget/runtime": "latest",
},
}}
files={afterFiles}
template="react"
content={children}
theme={theme === "system" || !theme ? "auto" : (theme as any)}
>
<CodeEditorAndPreview
readOnly
code={afterCode}
previewClassName={previewClassName}
/>
</SandpackProvider>
}
/>
<div>
<BeforeAfterCodeLayout
before={
<SandpackProvider
template="react"
files={beforeFiles}
theme={theme === "system" || !theme ? "auto" : (theme as any)}
>
<CodeEditorAndPreview
onChange={handleCodeChange}
previewClassName={previewClassName}
/>
</SandpackProvider>
}
after={
<SandpackProvider
customSetup={{
dependencies: {
"@react-unforget/runtime": "latest",
},
}}
files={afterFiles}
template="react"
content={children}
theme={theme === "system" || !theme ? "auto" : (theme as any)}
>
<CodeEditorAndPreview
readOnly
code={afterCode}
previewClassName={previewClassName}
/>
</SandpackProvider>
}
/>
<details
open={viewDependencyGraph}
className="collapse mt-10 border"
onToggle={handleToggleDependencyGraph}
>
<summary className="collapse-title font-medium">
Click here to {viewDependencyGraph ? "hide" : "view"} the dependency
graph
</summary>
<div className="collapse-content">
{viewDependencyGraph && (
<DynamicDependencyGraphViewer mermaidSyntax={mermaidSyntax} />
)}
</div>
</details>
</div>
);
}

Expand Down
79 changes: 79 additions & 0 deletions apps/docs/components/OldAndNewCodeReveal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use client";
import React from "react";
import {
TextRevealCard,
TextRevealCardDescription,
TextRevealCardTitle,
} from "./ui/text-reveal-card";
import { encode } from "html-entities";

const oldCode = `const UglyComponent = memo(() => {
const data = useData();
const filteredData = useMemo(() => {
const items = [];
for (let i = 0; i < 1000000000; i++) {
items.push(data[i]);
}
}, [data]);
const someComplexJsx = useMemo(() => (
<>
<DependentComponent1 data={filteredData} />
{/* ... */}
</>
), [dependency1, dependency2, filteredData]);
return <div>{someComplexJsx}</div>;
});
`;

/*
const NiceComponent = () => {
const data = useData();
const filteredData = [];
for (let i = 0; i < 1000000000; i++) {
filteredData.push(data[i]);
}
return (
<div>
<DependentComponent1 data={filteredData} />
{/* ... *}
</div>
);
}
*/
const newCode = `<span class="text-blue-400">const</span> NiceComponent = () => {
<span class="text-blue-400">const</span> data = <span class="text-pink-400">useData</span>();
<span class="text-blue-400">const</span> filteredData = [];
<span class="text-green-400">for</span> (<span class="text-blue-400">let</span> i = <span class="text-yellow-400">0</span>; i &lt; <span class="text-yellow-400">1000000000</span>; i++) {
filteredData.<span class="text-purple-400">push</span>(data[i]);
}
<span class="text-green-400">return</span> (
<span class="text-red-400">&lt;div&gt;</span>
<span class="text-red-400">&lt;DependentComponent1</span> <span class="text-orange-400">data=</span><span class="text-yellow-400">{filteredData}</span> <span class="text-red-400">/&gt;</span>
<span class="text-gray-400">{/* ... */}</span>
<span class="text-red-400">&lt;/div&gt;</span>
);
}\n\n
`;

export const OldAndNewCodeReveal = () => {
return (
<div className="flex items-center justify-center my-10 rounded-2xl w-full">
<TextRevealCard text={encode(oldCode)} revealText={newCode}>
<TextRevealCardTitle>
You don't need all that clutter!
</TextRevealCardTitle>
<TextRevealCardDescription>
👋 Wave goodbye to clutter! Shed the excess and use React Unforget
instead.
</TextRevealCardDescription>
</TextRevealCard>
</div>
);
};
Loading

0 comments on commit 89be8a2

Please sign in to comment.