@yahoo/uds-create-sidecar
v5.56.2
Published
Keywords
Readme
UDS Create Sidecar
@yahoo/uds-create-sidecar lets an Expo development build render Create
previews inside the customer's own app. The preview therefore uses the same
native packages, configuration, and package versions as that app.
The package contains the existing Sidecar pairing UI, preview-room client, authenticated and integrity-checked bundle loader, and renderer host. The only required component prop is an explicit module registry:
Create browser ── signed room token ──> UDSCreateSidecar
│
Customer app ── exact modules + versions ──┘
│
└──> real app dependenciesThe standalone app in apps/create-sidecar consumes this same package and is
the reference implementation.
Requirements
This first package targets Expo SDK 57 development builds with React Native 0.86. It is not supported in Expo Go because registered packages may contain native code.
Install the host and the shared JSON renderer as development dependencies:
bun add --dev --exact @yahoo/uds-create-sidecar @json-render/react
bunx expo install react-native-safe-area-contextInstall and configure every native package used by the design system in the
usual way. Sidecar does not replace that package's native setup.
UDSCreateSidecar must be rendered below the app's SafeAreaProvider.
What the package expects from the app
Every package Sidecar needs from the app is a declared peer dependency, so a missing one is reported at install time:
react,react-native,react-native-safe-area-context, andexpoare the app's own copies. Sidecar renders its pairing UI with them.@json-render/reactis the renderer every Create preview bundle imports. Sidecar never imports it itself, but the registry requires it as a module, so the app must install it and register it.
What the package carries
The preview-room client is built on Yjs and the Hocuspocus provider. Both are
compiled into the package, together with their lib0 and y-protocols
internals, so the app does not install them and the app's own versions never
matter. Compiling them in also lets the package replace lib0's Web Crypto
binding with a small shim over @noble/hashes, the package's one runtime
dependency, so React Native needs no Web Crypto polyfill. Yjs uses those values
only for client and document identifiers.
An app that also uses Yjs sees one console error the first time the Sidecar
screen loads, Yjs was already imported, shown as a LogBox error in a
development build. It is Yjs noticing a second copy of itself. Sidecar's
documents never cross into the app's Yjs, so the two copies never meet and the
message can be dismissed.
1. Define the host registry
Create one module that statically imports everything a downloaded preview may use. Metro must see these imports when it builds the app; downloaded JavaScript cannot discover or import an arbitrary installed native package later.
This example exposes react-native-maps, a package that is intentionally not
built into the standalone Sidecar:
// src/create-sidecar-registry.ts
import * as JsonRenderReact from "@json-render/react";
import { createUDSCreateSidecarRegistry } from "@yahoo/uds-create-sidecar/registry";
import * as React from "react";
import * as ReactJsxRuntime from "react/jsx-runtime";
import * as ReactNative from "react-native";
import * as ReactNativeMaps from "react-native-maps";
import appPackage from "../package.json";
export const createSidecarRegistry = createUDSCreateSidecarRegistry({
hostName: "Acme mobile app",
hostVersion: appPackage.version,
modules: {
"@json-render/react": JsonRenderReact,
react: React,
"react/jsx-runtime": ReactJsxRuntime,
"react-native": ReactNative,
"react-native-maps": ReactNativeMaps,
},
packageVersions: {
...appPackage.dependencies,
...appPackage.devDependencies,
},
});The versions for every exposed package must be exact strings such as 1.2.3,
not ranges such as ^1.2.3. Pin those dependencies in package.json or provide
exact versions from the app's lockfile. The registry rejects a missing or
non-exact version immediately.
Subpath imports must be registered separately, but they use the package root's
version. For example, registering react/jsx-dev-runtime still uses the
react version.
How Create checks the registry
Each preview build lists the packages it imports, the version the design system declares for each, and the exact import paths it reads. The connected host advertises the registry, and Create and the phone run the same comparison:
- A required package or import path that is not registered is missing.
- A registered version whose major or minor differs from the required version is a mismatch. Patch versions may differ.
While the phone is connected, Create's phone controls name the packages the host cannot provide. The phone repeats the check before evaluating a bundle and refuses with an error naming the package and, for a mismatch, both versions. Either side can move: install the version the design system declares, or update the version the design system declares for that package.
2. Add a development-only route
The app, rather than the component, owns the production gate. This lets the standalone Sidecar remain a normal release build while a customer app can make the route impossible to activate in production.
For Expo Router, guard the package load as well as the rendered route:
// app/create-preview.tsx
import { Redirect } from "expo-router";
import { createSidecarRegistry } from "../src/create-sidecar-registry";
declare const require: (
id: "@yahoo/uds-create-sidecar",
) => typeof import("@yahoo/uds-create-sidecar");
export default function CreatePreviewRoute() {
if (!__DEV__) return <Redirect href="/" />;
const { UDSCreateSidecar } = require("@yahoo/uds-create-sidecar");
return <UDSCreateSidecar registry={createSidecarRegistry} />;
}Use the equivalent compile-time development constant and production fallback
with another router. Do not gate only a navigation link: the route itself must
return before loading or rendering Sidecar. The package test compiles this
pattern with __DEV__ set to false and verifies that the Sidecar reference is
removed from the route's execution path.
The two entry points are split for this gate. @yahoo/uds-create-sidecar/registry
holds only the registry builder, imports nothing, and is safe to import at
module top level in every build, including production. The root entry holds the
pairing UI, the preview-room client, and the compiled-in Yjs, so it stays behind
the compile-time check.
3. Pair with Create
Open the development route. UDSCreateSidecar uses production Create by
default and shows the same origin chooser, short-code pairing, status UI,
disconnect behavior, and full-screen design preview as the standalone app. A
custom origin remains selected while the component is mounted; a new launch
returns to production.
Full-screen designs fill the device viewport and own their safe-area layout. Sidecar hides the native status bar while showing a design and restores the host's status bar when returning to settings or component previews. The floating Sidecar settings button stays inside the device's safe area.
It also accepts an existing Sidecar deep link when the host app routes that URL to the component; short-code pairing is the portable default for an embedded host with its own URL scheme.
When the host router mounts this screen from a deep link, pass the route values to the component. This avoids relying on a link event that may have fired before the route mounted:
import type { UDSCreateSidecarDeepLink } from "@yahoo/uds-create-sidecar";
import { useLocalSearchParams } from "expo-router";
const params = useLocalSearchParams<UDSCreateSidecarDeepLink>();
const deepLink =
params.origin && params.room && params.token && params.ws
? {
origin: params.origin,
room: params.room,
token: params.token,
ws: params.ws,
}
: undefined;
return (
<UDSCreateSidecar registry={createSidecarRegistry} deepLink={deepLink} />
);Create's QR code uses the uds-create-sidecar scheme. An embedded app must
route that scheme to its development-only preview screen to use QR pairing.
Apps that do not own that route should use the short pairing code.
In Create, open Connect phone, choose Connect Sidecar, and enter the code from the app. The embedded host advertises the registry's exact package versions through the existing preview protocol; no protocol or token changes are required.
Security boundary
The host downloads a bundle only from the selected Create origin, sends the room-scoped bearer token, limits the response to 5 MiB, and verifies the manifest's SHA-256 hash before evaluation. Module resolution is limited to the explicit registry.
This is not a JavaScript sandbox. An authorized Create bundle runs inside the development app's JavaScript process and can use normal JavaScript globals in addition to registered modules. Expose only modules intended for preview, use only a trusted Create deployment, and keep the route hard-disabled in production.
