Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Preview): add startRoute prop to override Provider default #868

Merged
merged 6 commits into from
Apr 5, 2023
Merged
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
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