Skip to content

Commit

Permalink
docs: "Un"compare Forgetti with Unforget
Browse files Browse the repository at this point in the history
  • Loading branch information
mohebifar committed Mar 12, 2024
1 parent a3dea54 commit ae2e075
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 58 deletions.
4 changes: 3 additions & 1 deletion apps/docs/components/BeforeAfterCodeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { PiNumberCircleOneFill, PiNumberCircleTwoFill } from "react-icons/pi";
export interface BeforeAfterCodeLayoutProps {
before: React.ReactNode;
after: React.ReactNode;
afterTitle?: React.ReactNode;
}

export function BeforeAfterCodeLayout({
before,
after,
afterTitle = "React Unforget Result",
}: BeforeAfterCodeLayoutProps) {
return (
<div className="xl:grid-cols-beforeAfterCodingBlocks grid-row-2 mt-6 grid w-full grid-cols-1 gap-4 xl:grid-rows-1">
Expand Down Expand Up @@ -37,7 +39,7 @@ export function BeforeAfterCodeLayout({
<div className="overflow-hidden rounded-lg border border-white/[0.08] bg-[#1d1c20]">
<div className="flex-1 py-4 pr-4 text-center font-bold">
<div className="flex items-center justify-center gap-2">
<PiNumberCircleTwoFill size={18} /> React Unforget Result
<PiNumberCircleTwoFill size={18} /> {afterTitle}
</div>
</div>

Expand Down
99 changes: 65 additions & 34 deletions apps/docs/components/LiveCodeSandpack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import reactUnforgetBabelPlugin, {

// @ts-ignore
import jsxBabelPlugin from "@babel/plugin-syntax-jsx";
import forgettiBabel from "forgetti";
import { useTheme } from "nextra-theme-docs";
import { useCallback, useEffect, useMemo, useState } from "react";
import { BeforeAfterCodeLayout } from "./BeforeAfterCodeLayout";
Expand All @@ -32,9 +33,14 @@ export default function App() {
export interface LiveCodeProps {
children: string;
previewClassName?: string;
plugin?: "unforget" | "forgetti";
}

function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
function LiveCodeSandpack({
children,
previewClassName,
plugin = "unforget",
}: LiveCodeProps) {
const [beforeCode, setBeforeCode] = useState(children);
const [afterCode, setAfterCode] = useState(defaultLoadingCode);
const [mermaidSyntax, setMermaidSyntax] = useState<any>(null);
Expand All @@ -45,12 +51,15 @@ function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
const { theme } = useTheme();

const transformCode = useCallback((content: string) => {
const babelPluginToUse =
plugin === "unforget" ? reactUnforgetBabelPlugin : forgettiBabel;
const result = transform(content, {
plugins: [
jsxBabelPlugin,
[
reactUnforgetBabelPlugin,
babelPluginToUse,
{
preset: "react",
_debug_onComponentAnalysisFinished: (component: any) => {
setMermaidSyntax(mermaidGraphFromComponent(component));
},
Expand All @@ -62,6 +71,16 @@ function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
return result?.code ?? "";
}, []);

const customSetup = useMemo(
() => ({
dependencies: {
[plugin === "unforget" ? "@react-unforget/runtime" : "forgetti"]:
"latest",
},
}),
[],
);

const handleCodeChange = useCallback((newCode: string) => {
setBeforeCode(newCode);
}, []);
Expand Down Expand Up @@ -116,40 +135,52 @@ function LiveCodeSandpack({ children, previewClassName }: LiveCodeProps) {
</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 className="relative">
<SandpackProvider
customSetup={customSetup}
files={afterFiles}
template="react"
content={children}
theme={theme === "system" || !theme ? "auto" : (theme as any)}
>
{plugin === "forgetti" ? (
<div className="absolute left-0 right-0 top-0 z-10 bg-yellow-800 bg-opacity-90 px-4 py-2 font-bold">
The following editor is a preview of Forgetti not Unforget
</div>
) : null}
<CodeEditorAndPreview
readOnly
code={afterCode}
previewClassName={previewClassName}
/>
</SandpackProvider>
</div>
}
afterTitle={
plugin === "forgetti" ? (
<span>
<span className="text-yellow-500">Forgetti</span> Result
</span>
) : undefined
}
/>
<details
open={viewDependencyGraph}
className="collapse mt-10 overflow-hidden rounded-lg border border-white/[0.08] bg-[#1d1c20]"
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>
{plugin === "unforget" && (
<details
open={viewDependencyGraph}
className="collapse mt-10 overflow-hidden rounded-lg border border-white/[0.08] bg-[#1d1c20]"
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
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@react-unforget/runtime": "0.1.0-alpha.7",
"@vercel/analytics": "^1.2.2",
"clsx": "^2.1.0",
"forgetti": "^0.8.4",
"framer-motion": "^11.0.8",
"html-entities": "^2.5.2",
"mermaid": "^10.9.0",
Expand Down
8 changes: 8 additions & 0 deletions apps/docs/pages/comparisons/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"forgetti": {
"title": "Forgetti vs. Unforget",
"theme": {
"layout": "full"
}
}
}
154 changes: 154 additions & 0 deletions apps/docs/pages/comparisons/forgetti.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { DynamicLiveCodeSandpack } from "@components/DynamicLiveCodeSandpack";

# Forgetti

[Forgetti](https://github.com/lxsmnsyc/forgetti) is also another alternative tool made to optimize React components at build time to make it run faster at runtime. But, when it comes to more complicated patterns, it generates failing code. For example, loops and mutations can lead to generating code that does not work.
Expand All @@ -9,3 +11,155 @@
| Breaking down JSX || ✖️ |
| Basic mutation detection || ✖️ |
| Control flows || Fails with while/for loops |

### Example

Ok, let's see how Forgetti performs with a simple example. We have a counter component that uses the `useState` hook. We will see how Forgetti handles this.

<DynamicLiveCodeSandpack previewClassName="h-44" plugin="forgetti">
{`import { useState } from "react";
export default function CounterWithMutationTracking() {
const [state, setState] = useState(0);
const text = "Yay!";
return (
<div>
<button onClick={() => setState(state + 1)}>Increment</button>
<div>
<span>Count: {state}</span> {text}
</div>
</div>
);
}
`}

</DynamicLiveCodeSandpack>

Yay 🎉! It worked. Now let's make it a bit more complex. Remember the example from the home page of Unforget? Let's make the test mutable.


```ts
let text = "The count is is: ";

if (state % 2 === 0) {
text += "even";
} else {
text += "odd";
}
```

<DynamicLiveCodeSandpack previewClassName="h-44" plugin="forgetti">
{`import { useState } from "react";
export default function CounterWithMutationTracking() {
const [state, setState] = useState(0);
let text = "The number is: ";
if (state % 2 === 0) {
text += "even";
} else {
text += "odd";
}
return (
<div>
<button onClick={() => setState(state + 1)}>Increment</button>
<div>
<span>Count: {state}</span> {text}
</div>
</div>
);
}
`}

</DynamicLiveCodeSandpack>

And that just failed. Forgetti is not able to handle this. It's not able to detect the mutation in the `text` variable. This is a limitation of Forgetti.

Now let's see how it handles loops.

<DynamicLiveCodeSandpack previewClassName="h-44" plugin="forgetti">

{`import { useState } from "react";
const useData = () => {
const [data, setData] = useState([]);
return {
data,
addData: (item) => {
setData([...data, item]);
},
};
};
export default function CounterWithMutationTracking() {
const { data, addData } = useData();
const filteredData = [];
for (let i = 0; i < data.length; i++) {
if (data[i] % 2 === 0) {
filteredData.push(data[i]);
}
}
return (
<div>
<button onClick={() => addData(data.length)}>Add</button>
<div>
Data:{" "}
{data.map((item) => (
<span key={item}>{item} </span>
))}
</div>
<div>
Filtered data:{" "}
{filteredData.map((item) => (
<span key={item}>{item} </span>
))}
</div>
</div>
);
}
`}
</DynamicLiveCodeSandpack>

Oh no! It failed again.

Ok, one more test. Let's see how it handles alias analysis.


<DynamicLiveCodeSandpack previewClassName="h-44" plugin="forgetti">
{`import { useState } from "react";
function Comp({ a, b }) {
const x = [];
x.push(a);
const y = x;
y.push(b);
return <div>n: {x.join(",")}</div>;
}
export default function App() {
const [state, setState] = useState(1);
return (
<div>
{/* We use a constant value for a, but change b */}
<Comp a={1} b={state} />
<button onClick={() => setState((p) => p + 1)}>click</button>
</div>
);
}
`}

</DynamicLiveCodeSandpack>

And it failed again. Click on the button and you will see that the value is not updated.
4 changes: 0 additions & 4 deletions packages/babel-plugin/src/models/segment/ComponentSegment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,10 +573,6 @@ export class ComponentSegment {
return hasHookCall(this.path, this.component.path);
}

isTransformable() {
return !this.hasHookCall() && this.isInReturnNetwork();
}

/**
* Apply the transformation to the segment
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,4 @@ export function optimizeSegmentDependencies(dependencies: SegmentDependency[]) {
});

return optimizedDependencies;
return optimizedDependencies.filter((dependency) =>
dependency.segment.isTransformable(),
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ export function preserveReferences(binding: Binding) {
newBinding.reference(reference);
});
} else {
console.error({
oldName,
// oldReferences: oldReferences.map((r) => r.getSource()),
});
throw new Error("Binding not found");
}
};
Expand Down
Loading

0 comments on commit ae2e075

Please sign in to comment.