Skip to content

Commit

Permalink
feat(Preview): add startRoute prop to override Provider default (#868)
Browse files Browse the repository at this point in the history
Co-authored-by: Danilo Woznica <danilowoz@gmail.com>
  • Loading branch information
SSHari and danilowoz authored Apr 5, 2023
1 parent b1ee2f7 commit bc28871
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 52 deletions.
9 changes: 9 additions & 0 deletions sandpack-client/src/clients/node/client.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
15 changes: 10 additions & 5 deletions sandpack-client/src/clients/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
findStartScriptPackageJson,
getMessageFromError,
writeBuffer,
generateRandomId,
} from "./client.utils";
import { loadPreviewIframe, setPreviewIframeProperties } from "./iframe.utils";
import { injectScriptToIframe } from "./inject-scripts";
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -243,19 +245,22 @@ export class SandpackNode extends SandpackClient {
private async globalListeners(): Promise<void> {
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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
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(
{
type: "console",
codesandbox: true,
log: Array.isArray(encodeMessage) ? encodeMessage[0] : encodeMessage,
channelId: scope.channelId,
},
"*"
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* 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() {
export function setupHistoryListeners({
scope,
}: {
scope: { channelId: string };
}) {
// @ts-ignore
const origHistoryProto = window.history.__proto__;

Expand All @@ -14,6 +18,7 @@ export function setupHistoryListeners() {
url,
back: historyPosition > 0,
forward: historyPosition < historyList.length - 1,
channelId: scope.channelId,
},
"*"
);
Expand Down
9 changes: 6 additions & 3 deletions sandpack-client/src/clients/node/inject-scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, "*");
Expand Down
2 changes: 1 addition & 1 deletion sandpack-react/src/components/Console/SandpackConsole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const SandpackConsole = React.forwardRef<

{standalone && (
<>
<DependenciesProgress />
<DependenciesProgress clientId={clientId} />
<iframe ref={iframe} />
</>
)}
Expand Down
4 changes: 3 additions & 1 deletion sandpack-react/src/components/Navigator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement>): JSX.Element => {
const [baseUrl, setBaseUrl] = React.useState<string>("");
const { sandpack, dispatch, listen } = useSandpack();

const [relativeUrl, setRelativeUrl] = React.useState<string>(
sandpack.startRoute ?? "/"
startRoute ?? sandpack.startRoute ?? "/"
);

const [backEnabled, setBackEnabled] = React.useState(false);
Expand Down
36 changes: 20 additions & 16 deletions sandpack-react/src/components/Preview/Preview.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,6 @@ export const Viewport: Story<PreviewProps> = (args) => (
</SandpackProvider>
);

Viewport.argTypes = {
viewportSize: {
control: {
type: "select",
options: [
"iPhone X",
"iPad",
"Pixel 2",
"Moto G4",
"Surface Duo",
"auto",
],
},
},
};

export const WithNavigator: React.FC = () => (
<SandpackProvider
files={{
Expand All @@ -84,6 +68,26 @@ export const WithNavigator: React.FC = () => (
</SandpackProvider>
);

export const MultipleRoutePreviews: React.FC = () => {
return (
<SandpackProvider
files={{
"/pages/index.js": `export default () => "Home"`,
"/pages/about.js": `export default () => "About"`,
"/pages/careers.js": `export default () => "Careers"`,
}}
options={{ startRoute: "/" }}
template="nextjs"
>
<SandpackLayout>
<SandpackPreview showNavigator />
<SandpackPreview startRoute="/about" showNavigator />
<SandpackPreview startRoute="/careers" showNavigator />
</SandpackLayout>
</SandpackProvider>
);
};

export const AutoResize: React.FC = () => (
<SandpackProvider
files={{
Expand Down
13 changes: 10 additions & 3 deletions sandpack-react/src/components/Preview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface PreviewProps {
showOpenNewtab?: boolean;
actionsChildren?: JSX.Element;
children?: JSX.Element;
startRoute?: string;
}

const previewClassName = css({
Expand Down Expand Up @@ -104,12 +105,14 @@ export const SandpackPreview = React.forwardRef<
actionsChildren = <></>,
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);
Expand Down Expand Up @@ -158,7 +161,11 @@ export const SandpackPreview = React.forwardRef<
{...props}
>
{showNavigator && (
<Navigator clientId={clientId} onURLChange={handleNewURL} />
<Navigator
clientId={clientId}
onURLChange={handleNewURL}
startRoute={startRoute}
/>
)}

<div className={classNames(c("preview-container"), previewClassName)}>
Expand Down
9 changes: 7 additions & 2 deletions sandpack-react/src/components/common/DependenciesProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ import { useSandpackPreviewProgress } from "../..";
import { css } from "../../styles";
import { fadeIn } from "../../styles/shared";

export const DependenciesProgress: React.FC = () => {
const progressMessage = useSandpackPreviewProgress(3_000);
export const DependenciesProgress: React.FC<{ clientId?: string }> = ({
clientId,
}) => {
const progressMessage = useSandpackPreviewProgress({
timeout: 3_000,
clientId,
});

if (!progressMessage) {
return null;
Expand Down
4 changes: 2 additions & 2 deletions sandpack-react/src/components/common/LoadingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ export const LoadingOverlay = ({
const [shouldShowStdout, setShouldShowStdout] = React.useState(false);

const loadingOverlayState = useLoadingOverlayState(clientId, loading);
const progressMessage = useSandpackPreviewProgress();
const { logs: stdoutData } = useSandpackShellStdout({});
const progressMessage = useSandpackPreviewProgress({ clientId });
const { logs: stdoutData } = useSandpackShellStdout({ clientId });

React.useEffect(() => {
let timer: NodeJS.Timer;
Expand Down
47 changes: 36 additions & 11 deletions sandpack-react/src/contexts/utils/useClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ interface SandpackConfigState {
status: SandpackStatus;
}

export type ClientPropsOverride = { startRoute?: string };

export interface UseClientOperations {
clients: Record<string, SandpackClientType>;
initializeSandpackIframe: () => void;
runSandpack: () => Promise<void>;
unregisterBundler: (clientId: string) => void;
registerBundler: (
iframe: HTMLIFrameElement,
clientId: string
clientId: string,
clientPropsOverride?: ClientPropsOverride
) => Promise<void>;
registerReactDevTools: (value: ReactDevToolsMode) => void;
addListener: (
Expand Down Expand Up @@ -85,7 +88,12 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => {
*/
const intersectionObserver = useRef<IntersectionObserver | null>(null);
const lazyAnchorRef = useRef<HTMLDivElement>(null);
const preregisteredIframes = useRef<Record<string, HTMLIFrameElement>>({});
const preregisteredIframes = useRef<
Record<
string,
{ iframe: HTMLIFrameElement; clientPropsOverride?: ClientPropsOverride }
>
>({});
const clients = useRef<Record<string, SandpackClientType>>({});
const timeoutHook = useRef<NodeJS.Timer | null>(null);
const unsubscribeClientListeners = useRef<
Expand All @@ -106,7 +114,8 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => {
const createClient = useCallback(
async (
iframe: HTMLIFrameElement,
clientId: string
clientId: string,
clientPropsOverride?: ClientPropsOverride
): Promise<SandpackClientType> => {
options ??= {};
customSetup ??= {};
Expand All @@ -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,
Expand Down Expand Up @@ -212,10 +221,15 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => {
if (clients.current[clientId]) {
clients.current[clientId].destroy();
}

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
);
})
);

Expand Down Expand Up @@ -273,11 +287,22 @@ export const useClient: UseClient = ({ options, customSetup }, filesState) => {
]);

const registerBundler = useCallback(
async (iframe: HTMLIFrameElement, clientId: string): Promise<void> => {
async (
iframe: HTMLIFrameElement,
clientId: string,
clientPropsOverride?: ClientPropsOverride
): Promise<void> => {
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]
Expand Down
Loading

1 comment on commit bc28871

@vercel
Copy link

@vercel vercel bot commented on bc28871 Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.