Skip to content

Commit

Permalink
refactor: No DI for createElement
Browse files Browse the repository at this point in the history
  • Loading branch information
Bob Fanger committed Aug 12, 2022
1 parent 11cb272 commit c0016d5
Show file tree
Hide file tree
Showing 12 changed files with 48 additions and 68 deletions.
28 changes: 17 additions & 11 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

Seamlessly use React components inside a Svelte app

Suports:

- Nesting (Slot & Children)
- Contexts
- SSR

# "Embrace, extend, and extinguish"

This preprocessor is intended as temporary solution when migrating an existing large React codebase.
Expand All @@ -17,35 +23,35 @@ Inside the Svelte template prepend the name of the component with `react:` prefi

Instead of `<Button>`, you'd write `<react:Button>`

```html
You're also able to use libraries from the react ecosystem, react-youtube for example:

```svelte
<script>
import MyReactComponent from "./MyReactComponent.jsx";
import YouTube from "react-youtube";
</script>
<react:MyReactComponent />
<react:YouTube videoId="AdNJ3fydeao" />
```

The preprocessor compiles this to:
The snippet above would be generate:

```html
```svelte
<script>
import sveltify from "svelte-preprocess-react/sveltify";
import { createElement } from "react";
import { createPortal } from "react-dom";
import ReactDOM from "react-dom/client";
import { renderToString } from "react-dom/server";
import MyReactComponent from "./MyReactComponent.jsx";
import YouTube from "react-youtube";
const React$MyReactComponent = sveltify(
MyReactComponent,
createElement,
const React$YouTube = sveltify(
YouTube,
createPortal,
ReactDOM,
renderToString
);
</script>
<React$MyReactComponent />
<React$YouTube videoId="AdNJ3fydeao" />
```

## Setup
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"type": "git",
"url": "https://github.com/bfanger/svelte-preprocess-react.git"
},
"version": "0.8.0",
"version": "0.9.0",
"license": "MIT",
"type": "module",
"scripts": {
Expand Down
11 changes: 1 addition & 10 deletions src/demo/components/Examples.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import sveltify from "$lib/sveltify";
import { createElement } from "react";
import { renderToString } from "react-dom/server";
import ClickerReact from "../../tests/fixtures/Clicker";
import CounterReact from "../react-components/Counter";
Expand All @@ -12,25 +11,17 @@
const Counter = sveltify(
CounterReact,
createElement,
createPortal,
ReactDOM,
renderToString
);
const Clicker = sveltify(
ClickerReact,
createElement,
createPortal,
ReactDOM,
renderToString
);
const Alert = sveltify(
AlertReact,
createElement,
createPortal,
ReactDOM,
renderToString
);
const Alert = sveltify(AlertReact, createPortal, ReactDOM, renderToString);
</script>

<Counter initial={10} onCount={console.info} />
Expand Down
14 changes: 4 additions & 10 deletions src/lib/internal/Bridge.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import type React from "react";
import type { ReactElement, ReactPortal } from "react";
import { createElement } from "react";
import useReadable from "../useReadable";
import SvelteToReactContext from "./SvelteToReactContext";
import Child from "./Child";
import type { TreeNode } from "./types";

export type BridgeProps = {
createElement: typeof React.createElement;
createPortal: (
children: React.ReactNode,
container: Element | DocumentFragment,
key?: null | string
) => ReactPortal;
) => React.ReactPortal;
node: TreeNode;
};
const Bridge: React.FC<BridgeProps> = ({
createElement,
createPortal,
node,
}) => {
const Bridge: React.FC<BridgeProps> = ({ createPortal, node }) => {
const target = useReadable(node.target);
const props = useReadable(node.props);
const slot = useReadable(node.slot);

if (!target) {
return null;
}
const children: ReactElement[] = node.nodes.map((subnode) => {
const children: React.ReactElement[] = node.nodes.map((subnode) => {
return createElement(Bridge, {
key: subnode.key,
createElement,
createPortal,
node: subnode,
});
Expand Down
10 changes: 5 additions & 5 deletions src/lib/internal/Child.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as React from "react";
import { useRef, useEffect, createElement, type FC } from "react";

type Props = {
el: HTMLElement | undefined;
};
const Child: React.FC<Props> = ({ el }) => {
const ref = React.useRef<HTMLElement>();
React.useEffect(() => {
const Child: FC<Props> = ({ el }) => {
const ref = useRef<HTMLElement>();
useEffect(() => {
if (!ref.current) {
return;
}
Expand All @@ -15,7 +15,7 @@ const Child: React.FC<Props> = ({ el }) => {
ref.current.appendChild(el);
}
}, [ref, el]);
return React.createElement("react-child", {
return createElement("react-child", {
ref,
style: { display: "contents" },
});
Expand Down
3 changes: 1 addition & 2 deletions src/lib/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ function transform(content: string, options: TransformOptions) {
let portal: string;
const imports = [
`import ${prefix}sveltify from "svelte-preprocess-react/sveltify"`,
`import { createElement as ${prefix}createElement} from "react"`,
];
if (options.react >= 18) {
imports.push(
Expand Down Expand Up @@ -120,7 +119,7 @@ function transform(content: string, options: TransformOptions) {
const script = compiled.ast.instance || (compiled.ast.module as Script);
const wrappers = components
.map((component) => {
return `const React$${component} = ${prefix}sveltify(${component}, ${prefix}createElement, ${portal}, ${prefix}ReactDOM${renderToString});`;
return `const React$${component} = ${prefix}sveltify(${component}, ${portal}, ${prefix}ReactDOM${renderToString});`;
})
.join(";");

Expand Down
6 changes: 3 additions & 3 deletions src/lib/sveltify.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createElement } from "react";
import type { ComponentClass, FunctionComponent } from "react";
import type { SvelteComponentTyped } from "svelte/internal";
import type ReactDOMServer from "react-dom/server";
Expand Down Expand Up @@ -27,7 +28,6 @@ type Sveltified<P> = ConstructorOf<SvelteComponentTyped<Omit<P, "children">>>;
*/
export default function sveltify<P>(
reactComponent: FunctionComponent<P> | ComponentClass<P>,
createElement: BridgeProps["createElement"],
createPortal: BridgeProps["createPortal"],
ReactDOMClient: any,
renderToString?: typeof ReactDOMServer.renderToString
Expand Down Expand Up @@ -98,12 +98,12 @@ export default function sveltify<P>(
};
const parent = init.parent ?? tree;
parent.nodes.push(node);
rerender({ createElement, createPortal, node: tree });
rerender({ createPortal, node: tree });
init.onDestroy(() => {
parent.nodes = parent.nodes.filter(
(n) => n.svelteInstance !== svelteInstance
);
rerender({ createElement, createPortal, node: tree });
rerender({ createPortal, node: tree });
});
return node;
},
Expand Down
9 changes: 2 additions & 7 deletions src/routes/context-react.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,8 @@
const ctx = useContext(Context);
return createElement("h1", {}, ctx);
};
const Provider = sveltify(
ProviderReact,
createElement,
createPortal,
ReactDOM
);
const Child = sveltify(ChildReact, createElement, createPortal, ReactDOM);
const Provider = sveltify(ProviderReact, createPortal, ReactDOM);
const Child = sveltify(ChildReact, createPortal, ReactDOM);
</script>

<Provider value="Hello from react context provider">
Expand Down
2 changes: 0 additions & 2 deletions src/routes/context-svelte.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<script lang="ts">
import reactify from "$lib/reactify";
import sveltify from "$lib/sveltify";
import { createElement } from "react";
import { createPortal } from "react-dom";
import ReactDOM from "react-dom/client";
import { setContext } from "svelte";
Expand All @@ -12,7 +11,6 @@
const DebugContextReact = reactify(DebugContext);
const DebugContextReactSvelte = sveltify(
DebugContextReact,
createElement,
createPortal,
ReactDOM
);
Expand Down
3 changes: 1 addition & 2 deletions src/routes/mini.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<script lang="ts">
import sveltify from "$lib/sveltify";
import { createElement } from "react";
import { createPortal } from "react-dom";
import ReactDOM from "react-dom/client";
function HelloWord() {
return createElement("div", null, "Hello world");
}
const Component = sveltify(HelloWord, createElement, createPortal, ReactDOM);
const Component = sveltify(HelloWord, createPortal, ReactDOM);
</script>

<Component />
4 changes: 1 addition & 3 deletions src/routes/playwright.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import { onMount } from "svelte";
import ClickerReact from "../tests/fixtures/Clicker";
import { createElement } from "react";
import { createPortal } from "react-dom";
export let reactVersion: number;
Expand All @@ -35,10 +34,9 @@
onMount(() => {
const win: any = window;
win.sveltify = sveltify;
win.createElement = createElement;
win.ReactDOM = ReactDOM;
win.ClickerReact = ClickerReact;
win.Clicker = sveltify(ClickerReact, createElement, createPortal, ReactDOM);
win.Clicker = sveltify(ClickerReact, createPortal, ReactDOM);
loading = false;
});
Expand Down
24 changes: 12 additions & 12 deletions src/tests/__snapshots__/preprocess.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Vitest Snapshot v1

exports[`svelte-preprocess-react > should import 'react-dom/server' when ssr is enabled 1`] = `
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import { createElement as React$$createElement} from \\"react\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
// @ts-nocheck
import Clicker from \\"./Clicker\\";
let count = 1;
const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
const React$Clicker = React$$sveltify(Clicker, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
<React$Clicker
{count}
Expand All @@ -19,9 +19,9 @@ const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$crea

exports[`svelte-preprocess-react > should inject a script tag 1`] = `
"<script>
import React$$sveltify from \\"svelte-preprocess-react/sveltify\\",import { createElement as React$$createElement} from \\"react\\",import React$$ReactDOM from \\"react-dom/client\\",import { createPortal as React$$createPortal} from \\"react-dom\\",import { renderToString as React$$renderToString } from \\"react-dom/server\\"
import React$$sveltify from \\"svelte-preprocess-react/sveltify\\",import React$$ReactDOM from \\"react-dom/client\\",import { createPortal as React$$createPortal} from \\"react-dom\\",import { renderToString as React$$renderToString } from \\"react-dom/server\\"
const React$Counter = React$$sveltify(Counter, React$$createElement, React$$createPortal, React$$ReactDOM, React$$renderToString);
const React$Counter = React$$sveltify(Counter, React$$createPortal, React$$ReactDOM, React$$renderToString);
</script>
<!-- Counter could be a global variable -->
Expand All @@ -30,12 +30,12 @@ const React$Counter = React$$sveltify(Counter, React$$createElement, React$$crea
`;

exports[`svelte-preprocess-react > should not import 'react-dom/server' when ssr is disabled 1`] = `
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import { createElement as React$$createElement} from \\"react\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\";
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\";
// @ts-nocheck
import Clicker from \\"./Clicker\\";
let count = 1;
const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$createPortal, React$$ReactDOM);</script>
const React$Clicker = React$$sveltify(Clicker, React$$createPortal, React$$ReactDOM);</script>
<React$Clicker
{count}
Expand All @@ -47,25 +47,25 @@ const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$crea
`;

exports[`svelte-preprocess-react > should portal slotted content as children 1`] = `
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import { createElement as React$$createElement} from \\"react\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
import Alert from \\"../../demo/react-components/Alert\\";
if (!Alert) {
// Bypass: 'Alert' is declared but its value is never read. (ts)
}
const React$Alert = React$$sveltify(Alert, React$$createElement, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
const React$Alert = React$$sveltify(Alert, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
<React$Alert>A simple primary alert—check it out!</React$Alert>
"
`;

exports[`svelte-preprocess-react > should process <react:component> tags 1`] = `
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import { createElement as React$$createElement} from \\"react\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
// @ts-nocheck
import Clicker from \\"./Clicker\\";
let count = 1;
const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
const React$Clicker = React$$sveltify(Clicker, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
<React$Clicker
{count}
Expand All @@ -77,12 +77,12 @@ const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$crea
`;

exports[`svelte-preprocess-react > should process <react:component> tags 2`] = `
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import { createElement as React$$createElement} from \\"react\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
"<script>import React$$sveltify from \\"svelte-preprocess-react/sveltify\\"; import React$$ReactDOM from \\"react-dom/client\\"; import { createPortal as React$$createPortal} from \\"react-dom\\"; import { renderToString as React$$renderToString } from \\"react-dom/server\\";
// @ts-nocheck
import Clicker from \\"./Clicker\\";
let count = 1;
const React$Clicker = React$$sveltify(Clicker, React$$createElement, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
const React$Clicker = React$$sveltify(Clicker, React$$createPortal, React$$ReactDOM, React$$renderToString);</script>
<h2>prop and event</h2>
<React$Clicker
Expand Down

0 comments on commit c0016d5

Please sign in to comment.