From a204747e04afa7592d0cbb5d9fb35d9cd0b2cd49 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 15 Oct 2024 11:51:46 +0100 Subject: [PATCH] feat(worker-playground): delay preview environment setup (#6962) --- .changeset/curly-jars-kick.md | 5 ++ .changeset/tasty-chefs-watch.md | 7 ++ packages/quick-edit/functions/_middleware.ts | 2 + packages/workers-playground/package.json | 1 - .../src/QuickEditor/EditorPane.tsx | 2 +- .../src/QuickEditor/HTTPTab/HTTPTab.tsx | 55 +++++++++----- .../src/QuickEditor/PreviewTab/PreviewTab.tsx | 5 +- .../src/QuickEditor/PreviewTab/UrlBar.tsx | 41 +++++------ .../src/QuickEditor/QuickEditor.tsx | 19 +++-- .../src/QuickEditor/useDraftWorker.ts | 73 +++++++------------ pnpm-lock.yaml | 3 - 11 files changed, 114 insertions(+), 99 deletions(-) create mode 100644 .changeset/curly-jars-kick.md create mode 100644 .changeset/tasty-chefs-watch.md diff --git a/.changeset/curly-jars-kick.md b/.changeset/curly-jars-kick.md new file mode 100644 index 000000000000..28284bb3ba0a --- /dev/null +++ b/.changeset/curly-jars-kick.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/quick-edit": minor +--- + +feat: lower autosave delay to 200ms diff --git a/.changeset/tasty-chefs-watch.md b/.changeset/tasty-chefs-watch.md new file mode 100644 index 000000000000..9a25a952b0d9 --- /dev/null +++ b/.changeset/tasty-chefs-watch.md @@ -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. diff --git a/packages/quick-edit/functions/_middleware.ts b/packages/quick-edit/functions/_middleware.ts index ccfdc6866be3..551222dce95c 100644 --- a/packages/quick-edit/functions/_middleware.ts +++ b/packages/quick-edit/functions/_middleware.ts @@ -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", }, diff --git a/packages/workers-playground/package.json b/packages/workers-playground/package.json index 2e65a302bbd7..e0303939059c 100644 --- a/packages/workers-playground/package.json +++ b/packages/workers-playground/package.json @@ -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", diff --git a/packages/workers-playground/src/QuickEditor/EditorPane.tsx b/packages/workers-playground/src/QuickEditor/EditorPane.tsx index bf431b5f26e6..acc095e5891d 100644 --- a/packages/workers-playground/src/QuickEditor/EditorPane.tsx +++ b/packages/workers-playground/src/QuickEditor/EditorPane.tsx @@ -22,7 +22,7 @@ export default function EditorPane() { : undefined } onChange={({ entrypoint, files }) => - draftWorker.preview({ entrypoint, modules: files }) + draftWorker.updateDraft({ entrypoint, modules: files }) } /> diff --git a/packages/workers-playground/src/QuickEditor/HTTPTab/HTTPTab.tsx b/packages/workers-playground/src/QuickEditor/HTTPTab/HTTPTab.tsx index 64c60a90d030..fed663dfca36 100644 --- a/packages/workers-playground/src/QuickEditor/HTTPTab/HTTPTab.tsx +++ b/packages/workers-playground/src/QuickEditor/HTTPTab/HTTPTab.tsx @@ -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"; @@ -57,22 +57,26 @@ export function HTTPTab() { setPreviewUrl, isPreviewUpdating, previewError, + preview, } = useContext(ServiceContext); const [method, setMethod] = useState("GET"); const [headers, setHeaders] = useState([]); const [body, setBody] = useState(""); const [response, setResponse] = useState(null); const [isLoading, setIsLoading] = useState(false); + const [url, setUrl] = useState(previewUrl); + const [refreshTimestamp, setRefreshTimestamp] = useState(); - const hasBody = method !== "HEAD" && method !== "GET" && method !== "OPTIONS"; + useEffect(() => { + setUrl(previewUrl); + }, [previewUrl]); - const onSendRequest = useCallback( - async (e?: React.FormEvent) => { - 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( @@ -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 (
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" @@ -124,10 +139,10 @@ export function HTTPTab() { /> setPreviewUrl(e.target.value)} + onChange={(e) => setUrl(e.target.value)} mb={0} />
diff --git a/packages/workers-playground/src/QuickEditor/QuickEditor.tsx b/packages/workers-playground/src/QuickEditor/QuickEditor.tsx index c6fa66f093f4..875e6032c1a1 100644 --- a/packages/workers-playground/src/QuickEditor/QuickEditor.tsx +++ b/packages/workers-playground/src/QuickEditor/QuickEditor.tsx @@ -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 === "") { diff --git a/packages/workers-playground/src/QuickEditor/useDraftWorker.ts b/packages/workers-playground/src/QuickEditor/useDraftWorker.ts index 9b575755ccac..98a815489b17 100644 --- a/packages/workers-playground/src/QuickEditor/useDraftWorker.ts +++ b/packages/workers-playground/src/QuickEditor/useDraftWorker.ts @@ -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"; @@ -139,14 +138,9 @@ async function compressWorker(worker: FormData) { ); } -async function updatePreviewHash( - content: Worker, - updateWorkerHash: (hash: string) => void -): Promise { +async function updatePreviewHash(content: Worker): Promise { const worker = serialiseWorker(content); const serialised = await compressWorker(worker); - const playgroundUrl = `/playground#${serialised}`; - updateWorkerHash(playgroundUrl); const res = await fetch("/playground/api/worker", { method: "POST", @@ -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) => void; + updateDraft: (content: Pick) => void; + preview: () => void; previewHash: PreviewHash | undefined; previewError: string | undefined; parseError: string | undefined; @@ -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>(); - + const [previewWorker, setPreviewWorker] = useState(draftWorker); const [previewHash, setPreviewHash] = useState(); const [previewError, setPreviewError] = useState(); const [devtoolsUrl, setDevtoolsUrl] = useState(); - const updatePreview = useDebounce( - async (wk?: Pick) => { - 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) { @@ -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, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index feccfacd0512..7b58a3d4172c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1487,9 +1487,6 @@ importers: '@cloudflare/util-en-garde': specifier: ^8.0.10 version: 8.0.10 - '@cloudflare/util-hooks': - specifier: ^1.2.0 - version: 1.3.1(react@18.3.1) '@cloudflare/workers-editor-shared': specifier: workspace:* version: link:../workers-editor-shared