From d557f9dd29a75cb2508af1eca31f5e2843a4d5ce Mon Sep 17 00:00:00 2001 From: SSHari Date: Sun, 2 Apr 2023 12:20:20 -0400 Subject: [PATCH 1/5] feat(Preview): add startRoute prop to override Provider default --- .../src/components/Navigator/index.tsx | 4 +- .../components/Preview/Preview.stories.tsx | 39 +++++++++++++++++ .../src/components/Preview/index.tsx | 13 ++++-- .../src/contexts/utils/useClient.ts | 43 +++++++++++++++---- sandpack-react/src/hooks/useSandpackClient.ts | 11 ++++- sandpack-react/src/types.ts | 5 ++- 6 files changed, 99 insertions(+), 16 deletions(-) diff --git a/sandpack-react/src/components/Navigator/index.tsx b/sandpack-react/src/components/Navigator/index.tsx index 72519a295..8c4a799ac 100644 --- a/sandpack-react/src/components/Navigator/index.tsx +++ b/sandpack-react/src/components/Navigator/index.tsx @@ -48,19 +48,21 @@ const inputClassName = css({ export interface NavigatorProps { clientId: string; onURLChange?: (newURL: string) => void; + startRoute?: string; } export const Navigator = ({ clientId, onURLChange, className, + startRoute, ...props }: NavigatorProps & React.HTMLAttributes): JSX.Element => { const [baseUrl, setBaseUrl] = React.useState(""); const { sandpack, dispatch, listen } = useSandpack(); const [relativeUrl, setRelativeUrl] = React.useState( - sandpack.startRoute ?? "/" + startRoute ?? sandpack.startRoute ?? "/" ); const [backEnabled, setBackEnabled] = React.useState(false); diff --git a/sandpack-react/src/components/Preview/Preview.stories.tsx b/sandpack-react/src/components/Preview/Preview.stories.tsx index f43f05c35..c242d4be6 100644 --- a/sandpack-react/src/components/Preview/Preview.stories.tsx +++ b/sandpack-react/src/components/Preview/Preview.stories.tsx @@ -84,6 +84,45 @@ export const WithNavigator: React.FC = () => ( ); +export const MultipleRoutePreviews: React.FC = () => { + const multiRoutePreviewCode = ` + import { createBrowserRouter, RouterProvider } from 'react-router-dom'; + + const router = createBrowserRouter([ + { path: "/about", element:
About!
}, + { path: "/careers", element:
Careers!
}, + ]); + + export default function MultiRoutePreviews() { + return ; + } + `; + + return ( + + + + + + + ); +}; + export const AutoResize: React.FC = () => ( , children, className, + startRoute, ...props }, ref ) => { - const { sandpack, listen, iframe, getClient, clientId } = - useSandpackClient(); + const { sandpack, listen, iframe, getClient, clientId } = useSandpackClient( + { startRoute } + ); const [iframeComputedHeight, setComputedAutoHeight] = React.useState< number | null >(null); @@ -158,7 +161,11 @@ export const SandpackPreview = React.forwardRef< {...props} > {showNavigator && ( - + )}
diff --git a/sandpack-react/src/contexts/utils/useClient.ts b/sandpack-react/src/contexts/utils/useClient.ts index 884d218f0..a2f42fd5c 100644 --- a/sandpack-react/src/contexts/utils/useClient.ts +++ b/sandpack-react/src/contexts/utils/useClient.ts @@ -34,6 +34,8 @@ interface SandpackConfigState { status: SandpackStatus; } +export type ClientPropsOverride = { startRoute?: string }; + export interface UseClientOperations { clients: Record; initializeSandpackIframe: () => void; @@ -41,7 +43,8 @@ export interface UseClientOperations { unregisterBundler: (clientId: string) => void; registerBundler: ( iframe: HTMLIFrameElement, - clientId: string + clientId: string, + clientPropsOverride?: ClientPropsOverride ) => Promise; registerReactDevTools: (value: ReactDevToolsMode) => void; addListener: ( @@ -85,7 +88,12 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => { */ const intersectionObserver = useRef(null); const lazyAnchorRef = useRef(null); - const preregisteredIframes = useRef>({}); + const preregisteredIframes = useRef< + Record< + string, + { iframe: HTMLIFrameElement; clientPropsOverride?: ClientPropsOverride } + > + >({}); const clients = useRef>({}); const timeoutHook = useRef(null); const unsubscribeClientListeners = useRef< @@ -106,7 +114,8 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => { const createClient = useCallback( async ( iframe: HTMLIFrameElement, - clientId: string + clientId: string, + clientPropsOverride?: ClientPropsOverride ): Promise => { options ??= {}; customSetup ??= {}; @@ -131,7 +140,7 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => { { externalResources: options.externalResources, bundlerURL: options.bundlerURL, - startRoute: options.startRoute, + startRoute: clientPropsOverride?.startRoute ?? options.startRoute, fileResolver: options.fileResolver, skipEval: options.skipEval ?? false, logLevel: options.logLevel, @@ -208,8 +217,13 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => { const runSandpack = useCallback(async (): Promise => { await Promise.all( Object.keys(preregisteredIframes.current).map(async (clientId) => { - const iframe = preregisteredIframes.current[clientId]; - clients.current[clientId] = await createClient(iframe, clientId); + const { iframe, clientPropsOverride = {} } = + preregisteredIframes.current[clientId]; + clients.current[clientId] = await createClient( + iframe, + clientId, + clientPropsOverride + ); }) ); @@ -267,11 +281,22 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => { ]); const registerBundler = useCallback( - async (iframe: HTMLIFrameElement, clientId: string): Promise => { + async ( + iframe: HTMLIFrameElement, + clientId: string, + clientPropsOverride?: ClientPropsOverride + ): Promise => { if (state.status === "running") { - clients.current[clientId] = await createClient(iframe, clientId); + clients.current[clientId] = await createClient( + iframe, + clientId, + clientPropsOverride + ); } else { - preregisteredIframes.current[clientId] = iframe; + preregisteredIframes.current[clientId] = { + iframe, + clientPropsOverride, + }; } }, [createClient, state.status] diff --git a/sandpack-react/src/hooks/useSandpackClient.ts b/sandpack-react/src/hooks/useSandpackClient.ts index 741d76360..e9155fa8d 100644 --- a/sandpack-react/src/hooks/useSandpackClient.ts +++ b/sandpack-react/src/hooks/useSandpackClient.ts @@ -8,6 +8,7 @@ import * as React from "react"; import type { SandpackState } from "../types"; import { generateRandomId } from "../utils/stringUtils"; +import type { ClientPropsOverride } from "../contexts/utils/useClient"; import { useSandpack } from "./useSandpack"; @@ -28,7 +29,9 @@ interface UseSandpackClient { * * @category Hooks */ -export const useSandpackClient = (): UseSandpackClient => { +export const useSandpackClient = ( + clientPropsOverride?: ClientPropsOverride +): UseSandpackClient => { const { sandpack, listen, dispatch } = useSandpack(); const iframeRef = React.useRef(null); const clientId = React.useRef(generateRandomId()); @@ -38,7 +41,11 @@ export const useSandpackClient = (): UseSandpackClient => { const clientIdValue = clientId.current; if (iframeElement !== null) { - sandpack.registerBundler(iframeElement, clientIdValue); + sandpack.registerBundler( + iframeElement, + clientIdValue, + clientPropsOverride + ); } return (): void => sandpack.unregisterBundler(clientIdValue); diff --git a/sandpack-react/src/types.ts b/sandpack-react/src/types.ts index a59d0a330..196964663 100644 --- a/sandpack-react/src/types.ts +++ b/sandpack-react/src/types.ts @@ -19,6 +19,8 @@ import type { SANDBOX_TEMPLATES } from "./templates"; import type { CodeEditorProps } from "."; +import type { ClientPropsOverride } from "./contexts/utils/useClient"; + /** * ------------------------ Public documentation ------------------------ * @@ -541,7 +543,8 @@ export interface SandpackState { runSandpack: () => Promise; registerBundler: ( iframe: HTMLIFrameElement, - clientId: string + clientId: string, + clientPropsOverride?: ClientPropsOverride ) => Promise; unregisterBundler: (clientId: string) => void; updateFile: ( From 00c4f82cd1981ec64488eb5fc9333bd293a771fd Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Wed, 5 Apr 2023 16:48:25 -0300 Subject: [PATCH 2/5] fix(client messages): create a channel id to support multiple clients --- .../src/clients/node/client.utils.ts | 9 ++++ sandpack-client/src/clients/node/index.ts | 15 ++++-- .../node/inject-scripts/consoleHook.ts | 5 ++ .../node/inject-scripts/historyListener.ts | 7 ++- .../src/clients/node/inject-scripts/index.ts | 9 ++-- .../components/Console/SandpackConsole.tsx | 2 +- .../components/Preview/Preview.stories.tsx | 51 +++---------------- .../common/DependenciesProgress.tsx | 9 +++- .../src/components/common/LoadingOverlay.tsx | 4 +- .../src/hooks/useSandpackPreviewProgress.ts | 16 ++++-- 10 files changed, 66 insertions(+), 61 deletions(-) diff --git a/sandpack-client/src/clients/node/client.utils.ts b/sandpack-client/src/clients/node/client.utils.ts index 1dc2467a2..7d9dc09b0 100644 --- a/sandpack-client/src/clients/node/client.utils.ts +++ b/sandpack-client/src/clients/node/client.utils.ts @@ -6,6 +6,15 @@ import { invariant } from "outvariant"; import type { SandpackBundlerFiles } from "../.."; import { createError } from "../.."; +let counter = 0; + +export function generateRandomId() { + const now = Date.now(); + const randomNumber = Math.round(Math.random() * 10000); + const count = (counter += 1); + return (+`${now}${randomNumber}${count}`).toString(16); +} + export const writeBuffer = ( content: string | Uint8Array, encoding: BufferEncoding = "utf8" diff --git a/sandpack-client/src/clients/node/index.ts b/sandpack-client/src/clients/node/index.ts index 0ab2ad903..7eb94ca86 100644 --- a/sandpack-client/src/clients/node/index.ts +++ b/sandpack-client/src/clients/node/index.ts @@ -27,6 +27,7 @@ import { findStartScriptPackageJson, getMessageFromError, writeBuffer, + generateRandomId, } from "./client.utils"; import { loadPreviewIframe, setPreviewIframeProperties } from "./iframe.utils"; import { injectScriptToIframe } from "./inject-scripts"; @@ -43,6 +44,7 @@ export class SandpackNode extends SandpackClient { private emulatorCommand: [string, string[], ShellCommandOptions] | undefined; private iframePreviewUrl: string | undefined; private _modulesCache = new Map(); + private messageChannelId = generateRandomId(); // Public public iframe!: HTMLIFrameElement; @@ -176,7 +178,7 @@ export class SandpackNode extends SandpackClient { const { url } = await this.emulator.preview.getByShellId(id); - this.iframePreviewUrl = url; + this.iframePreviewUrl = url + this.options.startRoute; } /** @@ -243,19 +245,22 @@ export class SandpackNode extends SandpackClient { private async globalListeners(): Promise { window.addEventListener("message", (event) => { if (event.data.type === PREVIEW_LOADED_MESSAGE_TYPE) { - injectScriptToIframe(this.iframe); + injectScriptToIframe(this.iframe, this.messageChannelId); } - if (event.data.type === "urlchange") { + if ( + event.data.type === "urlchange" && + event.data.channelId === this.messageChannelId + ) { this.dispatch({ type: "urlchange", url: event.data.url, back: event.data.back, forward: event.data.forward, }); + } else if (event.data.channelId === this.messageChannelId) { + this.dispatch(event.data); } - - this.dispatch(event.data); }); await this.emulator.fs.watch( diff --git a/sandpack-client/src/clients/node/inject-scripts/consoleHook.ts b/sandpack-client/src/clients/node/inject-scripts/consoleHook.ts index 626f5ccd3..2fc38d769 100644 --- a/sandpack-client/src/clients/node/inject-scripts/consoleHook.ts +++ b/sandpack-client/src/clients/node/inject-scripts/consoleHook.ts @@ -2,6 +2,10 @@ import Hook from "console-feed/lib/Hook"; import { Encode } from "console-feed/lib/Transform"; +declare global { + const scope: { channelId: string }; +} + Hook(window.console, (log) => { const encodeMessage = Encode(log) as any; parent.postMessage( @@ -9,6 +13,7 @@ Hook(window.console, (log) => { type: "console", codesandbox: true, log: Array.isArray(encodeMessage) ? encodeMessage[0] : encodeMessage, + channelId: scope.channelId, }, "*" ); diff --git a/sandpack-client/src/clients/node/inject-scripts/historyListener.ts b/sandpack-client/src/clients/node/inject-scripts/historyListener.ts index 850ccb212..537462860 100644 --- a/sandpack-client/src/clients/node/inject-scripts/historyListener.ts +++ b/sandpack-client/src/clients/node/inject-scripts/historyListener.ts @@ -1,6 +1,8 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/explicit-function-return-type, no-restricted-globals */ +/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/explicit-function-return-type, no-restricted-globals, @typescript-eslint/no-explicit-any */ + +export function setupHistoryListeners({ scope }: { scope: any }) { + const channelId = scope?.channelId; -export function setupHistoryListeners() { // @ts-ignore const origHistoryProto = window.history.__proto__; @@ -14,6 +16,7 @@ export function setupHistoryListeners() { url, back: historyPosition > 0, forward: historyPosition < historyList.length - 1, + channelId, }, "*" ); diff --git a/sandpack-client/src/clients/node/inject-scripts/index.ts b/sandpack-client/src/clients/node/inject-scripts/index.ts index 3fee782cb..b9facd7ff 100644 --- a/sandpack-client/src/clients/node/inject-scripts/index.ts +++ b/sandpack-client/src/clients/node/inject-scripts/index.ts @@ -11,18 +11,21 @@ import { setupHistoryListeners } from "./historyListener"; const scripts = [ { code: setupHistoryListeners.toString(), id: "historyListener" }, { - code: "function consoleHook() {" + consoleHook + "\n};", + code: "function consoleHook({ scope }) {" + consoleHook + "\n};", id: "consoleHook", }, ]; -export const injectScriptToIframe = (iframe: HTMLIFrameElement): void => { +export const injectScriptToIframe = ( + iframe: HTMLIFrameElement, + channelId: string +): void => { scripts.forEach(({ code, id }) => { const message: InjectMessage = { uid: id, type: INJECT_MESSAGE_TYPE, code: `exports.activate = ${code}`, - scope: {}, + scope: { channelId }, }; iframe.contentWindow?.postMessage(message, "*"); diff --git a/sandpack-react/src/components/Console/SandpackConsole.tsx b/sandpack-react/src/components/Console/SandpackConsole.tsx index c0818c633..4eb089ed6 100644 --- a/sandpack-react/src/components/Console/SandpackConsole.tsx +++ b/sandpack-react/src/components/Console/SandpackConsole.tsx @@ -192,7 +192,7 @@ export const SandpackConsole = React.forwardRef< {standalone && ( <> - +