remote-dom

repository·main·Indexed 23 days ago

https://github.com/shopify/remote-dom

Remote DOM enables rendering DOM trees created in sandboxed environments, such as Web Workers or iframes, onto the main page's DOM to facilitate code isolation and off-main-thread UI rendering. It provides utilities for synchronizing attributes, properties, events, and methods between environments, and includes a polyfill for DOM API support in non-browser environments. It is compatible with various libraries including React, Preact, Vue, Svelte, and vanilla JavaScript.

Tokens
34.1K
Snippets
82
Records
184
Agent score
72%

What's inside remote-dom

  1. Explore Remote DOM companion packages

    main

    Remote DOM provides several companion packages to @remote-dom/core to extend its functionality across different environments and frameworks:

    • @remote-dom/preact: Provides Preact wrapper components for the remote environment and allows mapping remote elements directly to Preact components on the host.
    • @remote-dom/react: Provides React wrapper components for the remote environment and allows mapping remote elements directly to React components on the host.
    • @remote-dom/polyfill: Provides a minimal polyfill of the DOM APIs required to run Remote DOM in non-DOM environments, such as a Web Worker.
    • @remote-dom/signals: Enables receiving remote updates into a tree of signals.
  2. Explore Remote DOM features in the Kitchen Sink example

    main

    The example-kitchen-sink demonstrates the breadth of Remote DOM's capabilities. It showcases:

    • Custom Elements: Using elements with properties, event listeners, and methods.
    • Execution Environments: Choosing between sandboxing remote code in an <iframe> or using the Remote DOM polyfill to run DOM libraries within a Web Worker.
    • Library Compatibility: Implementing the same UI using various technologies, including:
  3. What is SignalRemoteReceiver?

    main
    The SignalRemoteReceiver class maps a remote tree of DOM elements into a collection of @preact/signals Signal objects. It stores remote elements in a JavaScript representation where mutable properties and children are stored in signals. This enables fine-grained subscriptions and computed values based on the remote tree's contents. It is specifically used by the @remote-dom/preact library to map remote trees to Preact components.
  4. What is Remote DOM

    main

    Remote DOM is a library that allows you to create a tree of DOM elements in a sandboxed JavaScript environment (such as a Web Worker or a hidden <iframe>) and render them to the actual DOM in a different JavaScript environment (the main thread).

    This architecture is used to:

    • Isolate potentially untrusted code away from the main thread.
    • Run UI libraries (like Preact, Svelte, or React) inside a Web Worker to keep the main thread responsive.
    • Render a controlled set of UI elements to the main page from a lightweight sandbox.
  5. Use Custom Elements as a Remote UI Sandbox

    main

    Remote DOM can use an iframe as a remote sandbox to host UI components. In this pattern, you define custom elements (e.g., <ui-button>) inside the sandbox. This allows the remote environment to render specific, styled components that the host environment can interact with.

    To handle communication between the host and the sandbox (such as event handlers), you need a way to pass functions across the postMessage boundary. While this example uses @quilted/threads, you can use any library capable of passing functions between JavaScript environments, such as comlink.

  6. When to use @remote-dom/polyfill vs @remote-dom/core/polyfill

    main
    Use @remote-dom/polyfill directly only if you need a low-level polyfill with custom hooks. For most use cases where you want to automatically synchronize remote elements between environments, you should use @remote-dom/core/polyfill instead, which leverages this library's hooks to handle synchronization automatically.
  7. Prevent event bubbling issues in host components

    main

    When implementing event listeners in a host component using createRemoteComponentRenderer(), if your events bubble, calling the callback directly might trigger listeners on ancestor elements in the remote environment.

    To prevent this, you can:

    1. Manually check if event.target === event.currentTarget.
    2. Pass the event object directly to the callback. Remote DOM will automatically apply protection so the event is only dispatched if the target matches.
    // Option 1: Manual check
    const Card = createRemoteComponentRenderer(function Card({children, onClick}) {
      return (
        <ui-card
          onClick={(event) => {
            if (event.target === event.currentTarget) {
              onClick?.();
            }
          }}
        >
          {children}
        </ui-card>
      );
    });
    
    // Option 2: Pass event object directly (Automatic protection)
    const Card = createRemoteComponentRenderer(function Card({children, onClick}) {
      return <ui-card onClick={onClick}>{children}</ui-card>;
    });
  8. How Remote DOM works: Host vs Remote environments

    main

    Remote DOM operates using two distinct JavaScript environments:

    1. Host Environment: Runs on the main HTML page. It renders the actual, visible UI elements.
    2. Remote Environment: Runs in a sandbox (like an <iframe> or Web Worker). It renders an invisible version of the DOM that is mirrored to the host.

    You can mix and match any combination of technologies for both environments. For example, the remote environment could be a React app running in a Web Worker, while the host is a plain HTML page.

  9. Install @remote-dom/core

    main

    Install the @remote-dom/core package using your preferred package manager to access DOM-based utilities for synchronizing elements between JavaScript environments.

    npm install @remote-dom/core --save # npm
    pnpm install @remote-dom/core --save # pnpm
    yarn add @remote-dom/core # yarn
  10. Update React render call in the remote environment

    main

    In Remote DOM, you no longer use a custom createRoot from a remote-ui package. Instead, you create a standard DOM element (e.g., <remote-root>) and use the standard react-dom/client createRoot API to render your application to that element.

    // Replace this:
    
    import {createRemoteRoot} from '@remote-ui/core';
    import {createRoot} from '@remote-ui/react';
    
    const root = createRemoteRoot(/* ... */);
    createRoot(remoteRoot).render(<App />);
    
    // With this:
    
    import {createRoot} from 'react-dom/client';
    
    const root = document.createElement('remote-root');
    document.body.append(root);
    createRoot(root).render(<App />);