Skip to content

Commit

Permalink
feat(worker-playground): delay preview environment setup (#6962)
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung authored Oct 15, 2024
1 parent be88a62 commit a204747
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 99 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-jars-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/quick-edit": minor
---

feat: lower autosave delay to 200ms
7 changes: 7 additions & 0 deletions .changeset/tasty-chefs-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"workers-playground": minor
---

feat: delay preview environment setup until user

The preview environment will now be set up after the user clicks the send or go button.
2 changes: 2 additions & 0 deletions packages/quick-edit/functions/_middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const onRequest = async ({
"package.json": true,
"wrangler.toml": true,
},
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 200,
"telemetry.telemetryLevel": "off",
"window.menuBarVisibility": "hidden",
},
Expand Down
1 change: 0 additions & 1 deletion packages/workers-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"@cloudflare/style-container": "7.12.2",
"@cloudflare/style-provider": "^3.1.0",
"@cloudflare/util-en-garde": "^8.0.10",
"@cloudflare/util-hooks": "^1.2.0",
"@cloudflare/workers-editor-shared": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"glob-to-regexp": "^0.4.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/workers-playground/src/QuickEditor/EditorPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function EditorPane() {
: undefined
}
onChange={({ entrypoint, files }) =>
draftWorker.preview({ entrypoint, modules: files })
draftWorker.updateDraft({ entrypoint, modules: files })
}
/>
</FrameErrorBoundary>
Expand Down
55 changes: 35 additions & 20 deletions packages/workers-playground/src/QuickEditor/HTTPTab/HTTPTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Toast } from "@cloudflare/component-toast";
import { Div, Form, Label, Output } from "@cloudflare/elements";
import { isDarkMode, theme } from "@cloudflare/style-const";
import { createStyledComponent } from "@cloudflare/style-container";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { FrameError } from "../FrameErrorBoundary";
import { InputField } from "../InputField";
import { ServiceContext } from "../QuickEditor";
Expand Down Expand Up @@ -57,22 +57,26 @@ export function HTTPTab() {
setPreviewUrl,
isPreviewUpdating,
previewError,
preview,
} = useContext(ServiceContext);
const [method, setMethod] = useState<HTTPMethod>("GET");
const [headers, setHeaders] = useState<HeaderEntry[]>([]);
const [body, setBody] = useState("");
const [response, setResponse] = useState<Response | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [url, setUrl] = useState(previewUrl);
const [refreshTimestamp, setRefreshTimestamp] = useState<string>();

const hasBody = method !== "HEAD" && method !== "GET" && method !== "OPTIONS";
useEffect(() => {
setUrl(previewUrl);
}, [previewUrl]);

const onSendRequest = useCallback(
async (e?: React.FormEvent<HTMLFormElement>) => {
e?.preventDefault();
const hasBody = method !== "HEAD" && method !== "GET" && method !== "OPTIONS";

useEffect(() => {
async function sendRequest() {
if (previewHash !== undefined && previewUrl !== undefined) {
try {
setPreviewUrl(previewUrl);
setIsLoading(true);
setResponse(
await fetchWorker(
Expand All @@ -89,22 +93,33 @@ export function HTTPTab() {
setIsLoading(false);
}
}
},
[previewHash, setPreviewUrl, previewUrl, method, headers, hasBody, body]
);
const ensureDevtoolsConnected = useRef(false);
useEffect(() => {
if (!ensureDevtoolsConnected.current && previewHash && !isLoading) {
void onSendRequest();
ensureDevtoolsConnected.current = true;
}
}, [previewHash, isLoading, onSendRequest]);

void sendRequest();
}, [
refreshTimestamp,
previewHash,
previewUrl,
method,
headers,
hasBody,
body,
]);

return (
<Div display="flex" flexDirection="column" width="100%">
<Form
display="flex"
onSubmit={(e) => void onSendRequest(e)}
onSubmit={(e) => {
e.preventDefault();
preview();

if (url === previewUrl) {
setRefreshTimestamp(new Date().toISOString());
} else {
setPreviewUrl(url);
}
}}
p={2}
gap={2}
borderBottom="1px solid"
Expand All @@ -124,10 +139,10 @@ export function HTTPTab() {
/>
<InputField
name="http_request_url"
value={previewUrl}
value={url}
autoComplete="off"
spellCheck={false}
onChange={(e) => setPreviewUrl(e.target.value)}
onChange={(e) => setUrl(e.target.value)}
mb={0}
/>
<Button
Expand All @@ -138,8 +153,8 @@ export function HTTPTab() {
disabled={
!previewHash ||
Boolean(previewError) ||
!previewUrl ||
!previewUrl.startsWith("/")
!url ||
!url.startsWith("/")
}
data-tracking-name="send http tab request"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ function PreviewTabImplementation() {
return (
<Div display="flex" flexDirection="column" width="100%">
<UrlBar
initialURL={draftWorker.previewUrl}
onSubmit={(url) => {
draftWorker.preview();

if (url === draftWorker?.previewUrl) {
refresh();
} else {
draftWorker.setPreviewUrl(url);
}
}}
loading={isLoading}
loading={isLoading || draftWorker.isPreviewUpdating}
/>
{!firstLoad && !draftWorker?.previewError && (
<Div
Expand Down
41 changes: 20 additions & 21 deletions packages/workers-playground/src/QuickEditor/PreviewTab/UrlBar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from "@cloudflare/component-button";
import { Div } from "@cloudflare/elements";
import { createComponent } from "@cloudflare/style-container";
import { useState } from "react";
import { useEffect, useState } from "react";
import { InputField } from "../InputField";
import type React from "react";

Expand All @@ -16,43 +16,42 @@ const StyledForm = createComponent(
);

type Props = {
initialURL: string;
onSubmit: (url: string) => void;
loading: boolean;
};

export default function URLBar(props: Props) {
const [value, setValue] = useState("/");
export default function URLBar({ initialURL, onSubmit, loading }: Props) {
const [url, setUrl] = useState(initialURL);

const onChangeInputValue = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value: newValue } = e.target;
if (!newValue.startsWith("/")) {
setValue(`/${newValue}`);
} else {
setValue(newValue);
}
};
useEffect(() => {
setUrl(initialURL);
}, [initialURL]);

return (
<StyledForm
onSubmit={(e: React.FormEvent) => {
e.preventDefault();
props.onSubmit(value);
onSubmit(url);
}}
>
<Div display="flex" gap={2} width="100%">
<InputField
name="url"
autoComplete="off"
value={value}
onChange={onChangeInputValue}
value={url}
onChange={(event) => {
let newURL = event.target.value;

if (!newURL.startsWith("/")) {
newURL = `/${newURL}`;
}

setUrl(newURL);
}}
/>
<Button
type="primary"
inverted={true}
submit={true}
loading={props.loading}
>
Send
<Button type="primary" inverted={true} submit={true} loading={loading}>
Go
</Button>
</Div>
</StyledForm>
Expand Down
19 changes: 12 additions & 7 deletions packages/workers-playground/src/QuickEditor/QuickEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,19 @@ export default function QuickEditor() {
window.location.hash.slice(1)
);

function updateWorkerHash(hash: string) {
history.replaceState(null, "", hash);
}
const draftWorker = useDraftWorker(initialWorkerContentHash);

const draftWorker = useDraftWorker(
initialWorkerContentHash,
updateWorkerHash
);
useEffect(() => {
function updateWorkerHash(hash: string) {
history.replaceState(null, "", hash);
}

const hash = draftWorker.previewHash?.serialised;

if (hash) {
updateWorkerHash(`/playground#${hash}`);
}
}, [draftWorker.previewHash?.serialised]);

useEffect(() => {
if (initialWorkerContentHash === "") {
Expand Down
73 changes: 28 additions & 45 deletions packages/workers-playground/src/QuickEditor/useDraftWorker.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { eg } from "@cloudflare/util-en-garde";
import { useDebounce } from "@cloudflare/util-hooks";
import lzstring from "lz-string";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import useSWR from "swr";
import { v4 } from "uuid";
import { getPlaygroundWorker } from "./getPlaygroundWorker";
Expand Down Expand Up @@ -139,14 +138,9 @@ async function compressWorker(worker: FormData) {
);
}

async function updatePreviewHash(
content: Worker,
updateWorkerHash: (hash: string) => void
): Promise<PreviewHash> {
async function updatePreviewHash(content: Worker): Promise<PreviewHash> {
const worker = serialiseWorker(content);
const serialised = await compressWorker(worker);
const playgroundUrl = `/playground#${serialised}`;
updateWorkerHash(playgroundUrl);

const res = await fetch("/playground/api/worker", {
method: "POST",
Expand All @@ -170,16 +164,13 @@ async function updatePreviewHash(
};
}

const DEBOUNCE_TIMEOUT = 1000;

export function useDraftWorker(
initialHash: string,
updateWorkerHash: (hash: string) => void
): {
export function useDraftWorker(initialHash: string): {
isLoading: boolean;
service: Worker | null;
previewService: Worker | null;
devtoolsUrl: string | undefined;
preview: (content: Pick<Worker, "entrypoint" | "modules">) => void;
updateDraft: (content: Pick<Worker, "entrypoint" | "modules">) => void;
preview: () => void;
previewHash: PreviewHash | undefined;
previewError: string | undefined;
parseError: string | undefined;
Expand All @@ -191,30 +182,23 @@ export function useDraftWorker(
data: worker,
isLoading,
error,
} = useSWR(initialHash, getPlaygroundWorker);
} = useSWR(initialHash, getPlaygroundWorker, {
// There is no need to revalidate playground worker as it is rarely updated
revalidateOnFocus: false,
});

const [draftWorker, setDraftWorker] =
useState<Pick<Worker, "entrypoint" | "modules">>();

const [previewWorker, setPreviewWorker] = useState(draftWorker);
const [previewHash, setPreviewHash] = useState<PreviewHash>();
const [previewError, setPreviewError] = useState<string>();
const [devtoolsUrl, setDevtoolsUrl] = useState<string>();

const updatePreview = useDebounce(
async (wk?: Pick<Worker, "entrypoint" | "modules">) => {
setDraftWorker(wk);
if (worker === undefined) {
return;
}
useEffect(() => {
async function updatePreview(content: Worker) {
try {
setIsPreviewUpdating(true);
const hash = await updatePreviewHash(
{
...worker,
...(wk ?? draftWorker),
},
updateWorkerHash
);
const hash = await updatePreviewHash(content);
setPreviewHash(hash);
setDevtoolsUrl(hash.devtoolsUrl);
} catch (e: unknown) {
Expand All @@ -225,28 +209,27 @@ export function useDraftWorker(
} finally {
setIsPreviewUpdating(false);
}
},
DEBOUNCE_TIMEOUT
);
}

const initialPreview = useRef(false);
useEffect(() => {
if (worker && !initialPreview.current) {
initialPreview.current = true;
setIsPreviewUpdating(true);
void updatePreview(worker).then(() => setIsPreviewUpdating(false));
if (worker) {
void updatePreview({
...worker,
...previewWorker,
});
}
}, [updatePreview, worker]);
}, [worker, previewWorker]);

return {
isLoading,
service: worker ? { ...worker, ...draftWorker } : null,
preview: (...args) => {
// updatePreview is debounced, so call setPreviewHash outside of it
setPreviewHash(undefined);
setPreviewError(undefined);
void updatePreview(...args);
previewService: worker ? { ...worker, ...previewWorker } : null,
preview: () => {
if (previewWorker !== draftWorker) {
setPreviewHash(undefined);
setPreviewWorker(draftWorker);
}
},
updateDraft: setDraftWorker,
devtoolsUrl,
previewHash,
isPreviewUpdating,
Expand Down
Loading

0 comments on commit a204747

Please sign in to comment.