Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 20 additions & 88 deletions examples/kitchen-sink/app/host.tsx
Original file line number Diff line number Diff line change
@@ -1,128 +1,60 @@
import '@preact/signals';
import {render} from 'preact';
import {
RemoteRootRenderer,
RemoteFragmentRenderer,
createRemoteComponentRenderer,
SignalRemoteReceiver,
} from '@remote-dom/preact/host';
import {ThreadIframe, ThreadWebWorker} from '@quilted/threads';

import type {SandboxAPI} from './types.ts';
import {Button, Modal, Stack, Text, ControlPanel} from './host/components.tsx';
import {createState} from './host/state.ts';
import {createEndpoint, fromWebWorker, retain, release} from '@remote-ui/rpc';

import {Button, Stack, Text} from './host/components.tsx';
import {adaptToLegacyRemoteChannel} from '@remote-dom/compat';

// We will put any remote elements we want to render in this root element.
const uiRoot = document.querySelector('main')!;

// We use the `@quilted/threads` library to create a “thread” for our iframe,
// which lets us communicate over `postMessage` without having to worry about
// most of its complexities.
const iframe = document.querySelector('iframe')!;
const iframeSandbox = new ThreadIframe<SandboxAPI>(iframe);

// We also use the `@quilted/threads` library to create a “thread” around a Web
// Worker. We’ll run the same example code in both, depending on the `sandbox`
// state chosen by the user.
const worker = new Worker(
new URL('./remote/worker/sandbox.ts', import.meta.url),
{
type: 'module',
},
);
const workerSandbox = new ThreadWebWorker<SandboxAPI>(worker);

// We will use Preact to render remote elements in this example. The Preact
// helper library lets you do this by mapping the name of a remote element to
// a local Preact component. We’ve implemented the actual UI of our components in
// the `./host/components.tsx` file, but we need to wrap each one in the `createRemoteComponentRenderer()`
// helper function in order to get some Preact niceties, like automatic conversion
// of slots to element props, and using the instance of a Preact component as the
// target for methods called on matching remote elements.
interface RemoteAPI {
renderLegacy: (channel: any) => Promise<void>;
}

const endpoint = createEndpoint<RemoteAPI>(fromWebWorker(worker));

const components = new Map([
['ui-text', createRemoteComponentRenderer(Text)],
['ui-button', createRemoteComponentRenderer(Button)],
['ui-stack', createRemoteComponentRenderer(Stack)],
['ui-modal', createRemoteComponentRenderer(Modal)],
// The `remote-fragment` element is a special element created by Remote DOM when
// it needs an unstyled container for a list of elements. This is primarily used
// to convert elements passed as a prop to React or Preact components into a slotted
// element. The `RemoteFragmentRenderer` component is provided to render this element
// on the host.
['remote-fragment', RemoteFragmentRenderer],
]);

// We offload most of the complex state logic to this `createState()` function. We’re
// just leaving the key bit in this file: when the example or sandbox changes, we render
// the example in the chosen sandbox. The `createState()` passes us a fresh `receiver`
// each time. This object, a `SignalRemoteReceiver`, keeps track of the tree of elements
// rendered by the remote environment. We use this object later to render these trees
// to Preact components using the `RemoteRootRenderer` component.

const {receiver, example, sandbox} = createState(
async ({receiver, example, sandbox}) => {
const api = {
sandbox,
example,
async alert(content: string) {
console.log(
`Alert API used by example ${example} in the iframe sandbox`,
);
window.alert(content);
},
async closeModal() {
document.querySelector('dialog')?.close();
},
};

const sandboxToUse = sandbox === 'iframe' ? iframeSandbox : workerSandbox;

if (example === 'react-remote-ui') {
const remoteUiChannel = adaptToLegacyRemoteChannel(receiver.connection, {
elements: {
Text: 'ui-text',
Button: 'ui-button',
Stack: 'ui-stack',
Modal: 'ui-modal',
},
});
await sandboxToUse.imports.renderLegacy(remoteUiChannel, {
...api,
});
} else {
await sandboxToUse.imports.render(receiver.connection, {
...api,
});
}
const receiver = new SignalRemoteReceiver({retain, release});
const channel = adaptToLegacyRemoteChannel(receiver.connection, {
elements: {
Text: 'ui-text',
Button: 'ui-button',
Stack: 'ui-stack',
},
);
});
endpoint.call.renderLegacy(channel);

// We render our Preact application, including the part that renders any remote
// elements for the current example, and the control panel that lets us change
// the framework or JavaScript sandbox being used.
render(
<>
<ExampleRenderer />
<ControlPanel sandbox={sandbox} example={example} />
</>,
uiRoot,
);

function ExampleRenderer() {
const value = receiver.value;

if (value == null) return <div>Loading...</div>;

if ('then' in value) {
return <div>Rendering example...</div>;
}

if (value instanceof Error) {
return <div>Error while rendering example: {value.message}</div>;
}

return (
<div>
<RemoteRootRenderer receiver={value} components={components} />
<RemoteRootRenderer receiver={receiver} components={components} />
</div>
);
}
113 changes: 0 additions & 113 deletions examples/kitchen-sink/app/host/state.ts

This file was deleted.

17 changes: 0 additions & 17 deletions examples/kitchen-sink/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,6 @@
<body>
<main></main>

<!--
We use an iframe as the remote environment here, because it supports
modules in most modern browsers. Aside from support for ES modules, there
is nothing that prevents us from instead using a web worker as the remote
environment. Note that, without using a separate domain, or manually
hiding globals within the iframe, we are not actually creating a “safe”
sandbox here, as the remote code can reach up into the parent.
-->
<iframe
src="./remote/iframe/"
style="visibility: hidden; position: absolute"
></iframe>

<!--
This JavaScript will connect to the iframe, and begin propagating UI updates
into the UI element above.
-->
<script type="module" src="./host.tsx" crossorigin="anonymous"></script>
</body>
</html>
46 changes: 0 additions & 46 deletions examples/kitchen-sink/app/remote/examples/App.svelte

This file was deleted.

51 changes: 0 additions & 51 deletions examples/kitchen-sink/app/remote/examples/App.vue

This file was deleted.

Loading
Loading