From 755b6727fb74745b70ecd0bbd89c2c2e700141e7 Mon Sep 17 00:00:00 2001 From: SSHari Date: Sat, 15 Apr 2023 14:37:04 -0400 Subject: [PATCH 1/6] feat: add console hook to static template --- sandpack-client/rollup.config.js | 4 +- .../src/clients/node/inject-scripts/index.ts | 2 +- sandpack-client/src/clients/static/index.ts | 45 +++++++++++++++++++ .../node => }/inject-scripts/consoleHook.ts | 0 4 files changed, 48 insertions(+), 3 deletions(-) rename sandpack-client/src/{clients/node => }/inject-scripts/consoleHook.ts (100%) diff --git a/sandpack-client/rollup.config.js b/sandpack-client/rollup.config.js index 2bcd4be51..6ac089098 100644 --- a/sandpack-client/rollup.config.js +++ b/sandpack-client/rollup.config.js @@ -9,9 +9,9 @@ import pkg from "./package.json"; const configs = [ { - input: "src/clients/node/inject-scripts/consoleHook.ts", + input: "src/inject-scripts/consoleHook.ts", output: { - file: "src/clients/node/inject-scripts/dist/consoleHook.js", + file: "src/inject-scripts/dist/consoleHook.js", format: "es", }, plugins: [ diff --git a/sandpack-client/src/clients/node/inject-scripts/index.ts b/sandpack-client/src/clients/node/inject-scripts/index.ts index b9facd7ff..d08a6e9b9 100644 --- a/sandpack-client/src/clients/node/inject-scripts/index.ts +++ b/sandpack-client/src/clients/node/inject-scripts/index.ts @@ -5,7 +5,7 @@ import { INJECT_MESSAGE_TYPE } from "@codesandbox/nodebox"; // get the bundled file, which contains all dependencies // @ts-ignore -import consoleHook from "./dist/consoleHook.js"; +import consoleHook from "../../../inject-scripts/dist/consoleHook.js"; import { setupHistoryListeners } from "./historyListener"; const scripts = [ diff --git a/sandpack-client/src/clients/static/index.ts b/sandpack-client/src/clients/static/index.ts index 14b5c50d2..ddde797a4 100644 --- a/sandpack-client/src/clients/static/index.ts +++ b/sandpack-client/src/clients/static/index.ts @@ -15,6 +15,10 @@ import type { SandpackNodeMessage } from "../node/types"; import { insertHtmlAfterRegex, readBuffer, validateHtml } from "./utils"; +// get the bundled file, which contains all dependencies +// @ts-ignore +import consoleHook from "../../inject-scripts/dist/consoleHook.js"; + export class SandpackStatic extends SandpackClient { private emitter: EventEmitter; private previewController: PreviewController; @@ -50,6 +54,9 @@ export class SandpackStatic extends SandpackClient { content, options.externalResources ); + content = this.injectScriptIntoHead(content, { + script: consoleHook, + }); } catch (err) { console.error("Runtime injection failed", err); } @@ -80,6 +87,11 @@ export class SandpackStatic extends SandpackClient { ); } + this.eventListener = this.eventListener.bind(this); + if (typeof window !== "undefined") { + window.addEventListener("message", this.eventListener); + } + // Dispatch very first compile action this.updateSandbox(); } @@ -137,6 +149,21 @@ export class SandpackStatic extends SandpackClient { return this.injectContentIntoHead(content, tagsToInsert); } + private injectScriptIntoHead( + content: FileContent, + opts: { script: string; scope?: Record } + ): FileContent { + const { script, scope = {} } = opts; + const scriptToInsert = ` + + `.trim(); + + return this.injectContentIntoHead(content, scriptToInsert); + } + public updateSandbox( setup = this.sandboxSetup, _isInitializationCompile?: boolean @@ -169,6 +196,21 @@ export class SandpackStatic extends SandpackClient { }); } + // Handles message windows coming from iframes + private eventListener(evt: MessageEvent): void { + // skip events originating from different iframes + if (evt.source !== this.iframe.contentWindow) { + return; + } + + const message = evt.data; + if (!message.codesandbox) { + return; + } + + this.dispatch(message); + } + /** * Bundler communication */ @@ -190,5 +232,8 @@ export class SandpackStatic extends SandpackClient { public destroy(): void { this.emitter.cleanup(); + if (typeof window !== "undefined") { + window.removeEventListener("message", this.eventListener); + } } } diff --git a/sandpack-client/src/clients/node/inject-scripts/consoleHook.ts b/sandpack-client/src/inject-scripts/consoleHook.ts similarity index 100% rename from sandpack-client/src/clients/node/inject-scripts/consoleHook.ts rename to sandpack-client/src/inject-scripts/consoleHook.ts From 9bd024097c23c49d4c98b4c4aed2bc88bf51689a Mon Sep 17 00:00:00 2001 From: SSHari Date: Sun, 23 Apr 2023 00:54:24 -0400 Subject: [PATCH 2/6] Add optional listener options to track message type subscriptions --- sandpack-client/src/clients/base.ts | 7 +- sandpack-client/src/clients/node/index.ts | 9 ++- sandpack-client/src/clients/runtime/index.ts | 11 ++- sandpack-client/src/clients/static/index.ts | 46 +++++++++-- sandpack-client/src/types.ts | 22 +++++- .../components/Console/useSandpackConsole.ts | 78 ++++++++++--------- .../src/contexts/utils/useClient.ts | 48 ++++++++---- sandpack-react/src/types.ts | 11 ++- 8 files changed, 164 insertions(+), 68 deletions(-) diff --git a/sandpack-client/src/clients/base.ts b/sandpack-client/src/clients/base.ts index 366bd8c72..33df0c25f 100644 --- a/sandpack-client/src/clients/base.ts +++ b/sandpack-client/src/clients/base.ts @@ -6,6 +6,8 @@ import type { ListenerFunction, SandpackMessage, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../types"; export class SandpackClient { @@ -59,7 +61,10 @@ export class SandpackClient { throw Error("Method not implemented"); } - public listen(_listener: ListenerFunction): UnsubscribeFunction { + public listen( + _listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { throw Error("Method not implemented"); } } diff --git a/sandpack-client/src/clients/node/index.ts b/sandpack-client/src/clients/node/index.ts index 36c792c90..d77071f16 100644 --- a/sandpack-client/src/clients/node/index.ts +++ b/sandpack-client/src/clients/node/index.ts @@ -13,6 +13,8 @@ import type { ListenerFunction, SandboxSetup, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../.."; import { nullthrows } from "../.."; import { createError } from "../.."; @@ -434,8 +436,11 @@ export class SandpackNode extends SandpackClient { } } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.emitter.listener(listener); + public listen( + listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { + return this.emitter.listener(listener as any as ListenerFunction); } public destroy(): void { diff --git a/sandpack-client/src/clients/runtime/index.ts b/sandpack-client/src/clients/runtime/index.ts index 5b7c2e818..eaf843917 100644 --- a/sandpack-client/src/clients/runtime/index.ts +++ b/sandpack-client/src/clients/runtime/index.ts @@ -13,6 +13,8 @@ import type { SandpackBundlerFiles, SandpackError, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../../types"; import { SandpackLogLevel } from "../../types"; import { @@ -265,8 +267,13 @@ export class SandpackRuntime extends SandpackClient { this.iframeProtocol.dispatch(message); } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.iframeProtocol.channelListen(listener); + public listen( + listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { + return this.iframeProtocol.channelListen( + listener as any as ListenerFunction + ); } /** diff --git a/sandpack-client/src/clients/static/index.ts b/sandpack-client/src/clients/static/index.ts index ddde797a4..7d3027e5b 100644 --- a/sandpack-client/src/clients/static/index.ts +++ b/sandpack-client/src/clients/static/index.ts @@ -4,8 +4,11 @@ import { PreviewController } from "static-browser-server"; import type { ClientOptions, + ListenerOptions, ListenerFunction, SandboxSetup, + SandpackMessageType, + SandpackMessageByType, UnsubscribeFunction, } from "../.."; import { SandpackClient } from "../base"; @@ -23,6 +26,7 @@ export class SandpackStatic extends SandpackClient { private emitter: EventEmitter; private previewController: PreviewController; private files: Map = new Map(); + private registeredMessageTypes: Map = new Map(); public iframe!: HTMLIFrameElement; public selector!: string; @@ -54,9 +58,12 @@ export class SandpackStatic extends SandpackClient { content, options.externalResources ); - content = this.injectScriptIntoHead(content, { - script: consoleHook, - }); + + if (this.registeredMessageTypes.has("console")) { + content = this.injectScriptIntoHead(content, { + script: consoleHook, + }); + } } catch (err) { console.error("Runtime injection failed", err); } @@ -226,8 +233,37 @@ export class SandpackStatic extends SandpackClient { } } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.emitter.listener(listener); + public listen( + listener: ListenerFunction, + opts?: ListenerOptions + ): UnsubscribeFunction { + opts?.messageTypes?.forEach((type) => { + const count = this.registeredMessageTypes.get(type) ?? 0; + this.registeredMessageTypes.set(type, count + 1); + }); + + const unsubscribe = this.emitter.listener((message) => { + if ( + !opts?.messageTypes || + opts.messageTypes.includes(message.type as Type) + ) { + listener(message as SandpackMessageByType); + } + }); + + return () => { + unsubscribe(); + + opts?.messageTypes?.forEach((type) => { + const count = this.registeredMessageTypes.get(type)!; + + if (count === 1) { + this.registeredMessageTypes.delete(type); + } else { + this.registeredMessageTypes.set(type, count - 1); + } + }); + }; } public destroy(): void { diff --git a/sandpack-client/src/types.ts b/sandpack-client/src/types.ts index 3e11816ec..5b5ff7973 100644 --- a/sandpack-client/src/types.ts +++ b/sandpack-client/src/types.ts @@ -174,13 +174,27 @@ export type ClientStatus = | "idle"; export type SandpackMessage = SandpackRuntimeMessage | SandpackNodeMessage; +export type SandpackMessageType = SandpackMessage["type"]; +export type SandpackMessageByType = Extract< + SandpackMessage, + { type: Type } +>; -export type ListenerFunction = (msg: SandpackMessage) => void; +export type ListenerFunction< + Type extends SandpackMessageType = SandpackMessageType +> = (msg: SandpackMessageByType) => void; export type UnsubscribeFunction = () => void; -export type Listen = ( - listener: ListenerFunction, - clientId?: string +export type ListenerOptions< + Type extends SandpackMessageType = SandpackMessageType +> = { + messageTypes?: Array; +}; + +export type Listen = ( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; export type Dispatch = (msg: SandpackMessage, clientId?: string) => void; diff --git a/sandpack-react/src/components/Console/useSandpackConsole.ts b/sandpack-react/src/components/Console/useSandpackConsole.ts index 1080ba172..22d05b32f 100644 --- a/sandpack-react/src/components/Console/useSandpackConsole.ts +++ b/sandpack-react/src/components/Console/useSandpackConsole.ts @@ -29,52 +29,56 @@ export const useSandpackConsole = ({ const { listen } = useSandpack(); React.useEffect(() => { - const unsubscribe = listen((message) => { - if (resetOnPreviewRestart && message.type === "start") { - setLogs([]); - } else if (message.type === "console" && message.codesandbox) { - const payloadLog = Array.isArray(message.log) - ? message.log - : [message.log]; + const unsubscribe = listen( + (message) => { + if (resetOnPreviewRestart && message.type === "start") { + setLogs([]); + } else if (message.type === "console" && message.codesandbox) { + const payloadLog = Array.isArray(message.log) + ? message.log + : [message.log]; - if (payloadLog.find(({ method }) => method === "clear")) { - return setLogs([CLEAR_LOG]); - } + if (payloadLog.find(({ method }) => method === "clear")) { + return setLogs([CLEAR_LOG]); + } - const logsMessages = showSyntaxError - ? payloadLog - : payloadLog.filter((messageItem) => { - const messagesWithoutSyntaxErrors = - messageItem?.data?.filter?.((dataItem) => { - if (typeof dataItem !== "string") return true; + const logsMessages = showSyntaxError + ? payloadLog + : payloadLog.filter((messageItem) => { + const messagesWithoutSyntaxErrors = + messageItem?.data?.filter?.((dataItem) => { + if (typeof dataItem !== "string") return true; - const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => - dataItem.startsWith(lookFor) - ); + const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => + dataItem.startsWith(lookFor) + ); - return matches.length === 0; - }) ?? []; + return matches.length === 0; + }) ?? []; - return messagesWithoutSyntaxErrors.length > 0; - }); + return messagesWithoutSyntaxErrors.length > 0; + }); - if (!logsMessages) return; + if (!logsMessages) return; - setLogs((prev) => { - const messages = [...prev, ...logsMessages].filter( - (value, index, self) => { - return index === self.findIndex((s) => s.id === value.id); - } - ); + setLogs((prev) => { + const messages = [...prev, ...logsMessages].filter( + (value, index, self) => { + return index === self.findIndex((s) => s.id === value.id); + } + ); - while (messages.length > maxMessageCount) { - messages.shift(); - } + while (messages.length > maxMessageCount) { + messages.shift(); + } - return messages; - }); - } - }, clientId); + return messages; + }); + } + }, + clientId, + { messageTypes: ["console", "start"] } + ); return unsubscribe; }, [showSyntaxError, maxMessageCount, clientId, resetOnPreviewRestart]); diff --git a/sandpack-react/src/contexts/utils/useClient.ts b/sandpack-react/src/contexts/utils/useClient.ts index a8539adab..f0ba7f3c7 100644 --- a/sandpack-react/src/contexts/utils/useClient.ts +++ b/sandpack-react/src/contexts/utils/useClient.ts @@ -6,6 +6,8 @@ import type { SandpackMessage, UnsubscribeFunction, SandpackClient, + SandpackMessageType, + ListenerOptions, } from "@codesandbox/sandpack-client"; import { loadSandpackClient, @@ -49,9 +51,10 @@ export interface UseClientOperations { clientPropsOverride?: ClientPropsOverride ) => Promise; registerReactDevTools: (value: ReactDevToolsMode) => void; - addListener: ( + addListener: ( listener: ListenerFunction, - clientId?: string + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; dispatchMessage: (message: SandpackMessage, clientId?: string) => void; lazyAnchorRef: React.RefObject; @@ -61,7 +64,10 @@ export interface UseClientOperations { Record> >; queuedListenersRef: React.MutableRefObject< - Record> + Record< + string, + Record + > >; } @@ -106,7 +112,10 @@ export const useClient: UseClient = ( >({}); const unsubscribe = useRef<() => void | undefined>(); const queuedListeners = useRef< - Record> + Record< + string, + Record + > >({ global: {} }); const debounceHook = useRef(); const loadingScreenRegisteredRef = useRef(true); @@ -185,8 +194,9 @@ export const useClient: UseClient = ( */ if (queuedListeners.current[clientId]) { Object.keys(queuedListeners.current[clientId]).forEach((listenerId) => { - const listener = queuedListeners.current[clientId][listenerId]; - const unsubscribe = client.listen(listener) as () => void; + const { listener, opts } = + queuedListeners.current[clientId][listenerId]; + const unsubscribe = client.listen(listener, opts) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; }); @@ -199,8 +209,8 @@ export const useClient: UseClient = ( * Register global listeners */ const globalListeners = Object.entries(queuedListeners.current.global); - globalListeners.forEach(([listenerId, listener]) => { - const unsubscribe = client.listen(listener) as () => void; + globalListeners.forEach(([listenerId, { listener, opts }]) => { + const unsubscribe = client.listen(listener, opts) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; /** @@ -392,13 +402,17 @@ export const useClient: UseClient = ( } }; - const addListener = ( - listener: ListenerFunction, - clientId?: string + const addListener = ( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ): UnsubscribeFunction => { if (clientId) { if (clients.current[clientId]) { - const unsubscribeListener = clients.current[clientId].listen(listener); + const unsubscribeListener = clients.current[clientId].listen( + listener, + opts + ); return unsubscribeListener; } else { @@ -413,7 +427,10 @@ export const useClient: UseClient = ( unsubscribeClientListeners.current[clientId] = unsubscribeClientListeners.current[clientId] || {}; - queuedListeners.current[clientId][listenerId] = listener; + queuedListeners.current[clientId][listenerId] = { + listener: listener as any as ListenerFunction, + opts: opts as any as ListenerOptions, + }; const unsubscribeListener = (): void => { if (queuedListeners.current[clientId][listenerId]) { @@ -437,7 +454,10 @@ export const useClient: UseClient = ( } else { // Push to the **global** queue const listenerId = generateRandomId(); - queuedListeners.current.global[listenerId] = listener; + queuedListeners.current.global[listenerId] = { + listener: listener as any as ListenerFunction, + opts: opts as any as ListenerOptions, + }; // Add to the current clients const clientsList = Object.values(clients.current); diff --git a/sandpack-react/src/types.ts b/sandpack-react/src/types.ts index 0510b3cb7..aef7ee1ac 100644 --- a/sandpack-react/src/types.ts +++ b/sandpack-react/src/types.ts @@ -12,6 +12,8 @@ import type { UnsubscribeFunction, SandpackLogLevel, NpmRegistry, + SandpackMessageType, + ListenerOptions, } from "@codesandbox/sandpack-client"; import type React from "react"; @@ -517,9 +519,12 @@ export type SandpackClientDispatch = ( clientId?: string ) => void; -export type SandpackClientListen = ( - listener: ListenerFunction, - clientId?: string +export type SandpackClientListen = < + Type extends SandpackMessageType = SandpackMessageType +>( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; export type SandpackContext = SandpackState & { From a6ffc4c787aa4ba6eb4bd71d597291555ea3d491 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 8 Sep 2023 14:41:03 +0100 Subject: [PATCH 3/6] Revert "Add optional listener options to track message type subscriptions" This reverts commit 9bd024097c23c49d4c98b4c4aed2bc88bf51689a. --- sandpack-client/src/clients/base.ts | 7 +- sandpack-client/src/clients/node/index.ts | 9 +-- sandpack-client/src/clients/runtime/index.ts | 11 +-- sandpack-client/src/clients/static/index.ts | 46 ++--------- sandpack-client/src/types.ts | 22 +----- .../components/Console/useSandpackConsole.ts | 78 +++++++++---------- .../src/contexts/utils/useClient.ts | 48 ++++-------- sandpack-react/src/types.ts | 11 +-- 8 files changed, 68 insertions(+), 164 deletions(-) diff --git a/sandpack-client/src/clients/base.ts b/sandpack-client/src/clients/base.ts index 33df0c25f..366bd8c72 100644 --- a/sandpack-client/src/clients/base.ts +++ b/sandpack-client/src/clients/base.ts @@ -6,8 +6,6 @@ import type { ListenerFunction, SandpackMessage, UnsubscribeFunction, - SandpackMessageType, - ListenerOptions, } from "../types"; export class SandpackClient { @@ -61,10 +59,7 @@ export class SandpackClient { throw Error("Method not implemented"); } - public listen( - _listener: ListenerFunction, - _opts?: ListenerOptions - ): UnsubscribeFunction { + public listen(_listener: ListenerFunction): UnsubscribeFunction { throw Error("Method not implemented"); } } diff --git a/sandpack-client/src/clients/node/index.ts b/sandpack-client/src/clients/node/index.ts index d77071f16..36c792c90 100644 --- a/sandpack-client/src/clients/node/index.ts +++ b/sandpack-client/src/clients/node/index.ts @@ -13,8 +13,6 @@ import type { ListenerFunction, SandboxSetup, UnsubscribeFunction, - SandpackMessageType, - ListenerOptions, } from "../.."; import { nullthrows } from "../.."; import { createError } from "../.."; @@ -436,11 +434,8 @@ export class SandpackNode extends SandpackClient { } } - public listen( - listener: ListenerFunction, - _opts?: ListenerOptions - ): UnsubscribeFunction { - return this.emitter.listener(listener as any as ListenerFunction); + public listen(listener: ListenerFunction): UnsubscribeFunction { + return this.emitter.listener(listener); } public destroy(): void { diff --git a/sandpack-client/src/clients/runtime/index.ts b/sandpack-client/src/clients/runtime/index.ts index eaf843917..5b7c2e818 100644 --- a/sandpack-client/src/clients/runtime/index.ts +++ b/sandpack-client/src/clients/runtime/index.ts @@ -13,8 +13,6 @@ import type { SandpackBundlerFiles, SandpackError, UnsubscribeFunction, - SandpackMessageType, - ListenerOptions, } from "../../types"; import { SandpackLogLevel } from "../../types"; import { @@ -267,13 +265,8 @@ export class SandpackRuntime extends SandpackClient { this.iframeProtocol.dispatch(message); } - public listen( - listener: ListenerFunction, - _opts?: ListenerOptions - ): UnsubscribeFunction { - return this.iframeProtocol.channelListen( - listener as any as ListenerFunction - ); + public listen(listener: ListenerFunction): UnsubscribeFunction { + return this.iframeProtocol.channelListen(listener); } /** diff --git a/sandpack-client/src/clients/static/index.ts b/sandpack-client/src/clients/static/index.ts index 7d3027e5b..ddde797a4 100644 --- a/sandpack-client/src/clients/static/index.ts +++ b/sandpack-client/src/clients/static/index.ts @@ -4,11 +4,8 @@ import { PreviewController } from "static-browser-server"; import type { ClientOptions, - ListenerOptions, ListenerFunction, SandboxSetup, - SandpackMessageType, - SandpackMessageByType, UnsubscribeFunction, } from "../.."; import { SandpackClient } from "../base"; @@ -26,7 +23,6 @@ export class SandpackStatic extends SandpackClient { private emitter: EventEmitter; private previewController: PreviewController; private files: Map = new Map(); - private registeredMessageTypes: Map = new Map(); public iframe!: HTMLIFrameElement; public selector!: string; @@ -58,12 +54,9 @@ export class SandpackStatic extends SandpackClient { content, options.externalResources ); - - if (this.registeredMessageTypes.has("console")) { - content = this.injectScriptIntoHead(content, { - script: consoleHook, - }); - } + content = this.injectScriptIntoHead(content, { + script: consoleHook, + }); } catch (err) { console.error("Runtime injection failed", err); } @@ -233,37 +226,8 @@ export class SandpackStatic extends SandpackClient { } } - public listen( - listener: ListenerFunction, - opts?: ListenerOptions - ): UnsubscribeFunction { - opts?.messageTypes?.forEach((type) => { - const count = this.registeredMessageTypes.get(type) ?? 0; - this.registeredMessageTypes.set(type, count + 1); - }); - - const unsubscribe = this.emitter.listener((message) => { - if ( - !opts?.messageTypes || - opts.messageTypes.includes(message.type as Type) - ) { - listener(message as SandpackMessageByType); - } - }); - - return () => { - unsubscribe(); - - opts?.messageTypes?.forEach((type) => { - const count = this.registeredMessageTypes.get(type)!; - - if (count === 1) { - this.registeredMessageTypes.delete(type); - } else { - this.registeredMessageTypes.set(type, count - 1); - } - }); - }; + public listen(listener: ListenerFunction): UnsubscribeFunction { + return this.emitter.listener(listener); } public destroy(): void { diff --git a/sandpack-client/src/types.ts b/sandpack-client/src/types.ts index 5b5ff7973..3e11816ec 100644 --- a/sandpack-client/src/types.ts +++ b/sandpack-client/src/types.ts @@ -174,27 +174,13 @@ export type ClientStatus = | "idle"; export type SandpackMessage = SandpackRuntimeMessage | SandpackNodeMessage; -export type SandpackMessageType = SandpackMessage["type"]; -export type SandpackMessageByType = Extract< - SandpackMessage, - { type: Type } ->; -export type ListenerFunction< - Type extends SandpackMessageType = SandpackMessageType -> = (msg: SandpackMessageByType) => void; +export type ListenerFunction = (msg: SandpackMessage) => void; export type UnsubscribeFunction = () => void; -export type ListenerOptions< - Type extends SandpackMessageType = SandpackMessageType -> = { - messageTypes?: Array; -}; - -export type Listen = ( - listener: ListenerFunction, - clientId?: string, - opts?: ListenerOptions +export type Listen = ( + listener: ListenerFunction, + clientId?: string ) => UnsubscribeFunction; export type Dispatch = (msg: SandpackMessage, clientId?: string) => void; diff --git a/sandpack-react/src/components/Console/useSandpackConsole.ts b/sandpack-react/src/components/Console/useSandpackConsole.ts index 22d05b32f..1080ba172 100644 --- a/sandpack-react/src/components/Console/useSandpackConsole.ts +++ b/sandpack-react/src/components/Console/useSandpackConsole.ts @@ -29,56 +29,52 @@ export const useSandpackConsole = ({ const { listen } = useSandpack(); React.useEffect(() => { - const unsubscribe = listen( - (message) => { - if (resetOnPreviewRestart && message.type === "start") { - setLogs([]); - } else if (message.type === "console" && message.codesandbox) { - const payloadLog = Array.isArray(message.log) - ? message.log - : [message.log]; + const unsubscribe = listen((message) => { + if (resetOnPreviewRestart && message.type === "start") { + setLogs([]); + } else if (message.type === "console" && message.codesandbox) { + const payloadLog = Array.isArray(message.log) + ? message.log + : [message.log]; - if (payloadLog.find(({ method }) => method === "clear")) { - return setLogs([CLEAR_LOG]); - } - - const logsMessages = showSyntaxError - ? payloadLog - : payloadLog.filter((messageItem) => { - const messagesWithoutSyntaxErrors = - messageItem?.data?.filter?.((dataItem) => { - if (typeof dataItem !== "string") return true; + if (payloadLog.find(({ method }) => method === "clear")) { + return setLogs([CLEAR_LOG]); + } - const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => - dataItem.startsWith(lookFor) - ); + const logsMessages = showSyntaxError + ? payloadLog + : payloadLog.filter((messageItem) => { + const messagesWithoutSyntaxErrors = + messageItem?.data?.filter?.((dataItem) => { + if (typeof dataItem !== "string") return true; - return matches.length === 0; - }) ?? []; + const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => + dataItem.startsWith(lookFor) + ); - return messagesWithoutSyntaxErrors.length > 0; - }); + return matches.length === 0; + }) ?? []; - if (!logsMessages) return; + return messagesWithoutSyntaxErrors.length > 0; + }); - setLogs((prev) => { - const messages = [...prev, ...logsMessages].filter( - (value, index, self) => { - return index === self.findIndex((s) => s.id === value.id); - } - ); + if (!logsMessages) return; - while (messages.length > maxMessageCount) { - messages.shift(); + setLogs((prev) => { + const messages = [...prev, ...logsMessages].filter( + (value, index, self) => { + return index === self.findIndex((s) => s.id === value.id); } + ); - return messages; - }); - } - }, - clientId, - { messageTypes: ["console", "start"] } - ); + while (messages.length > maxMessageCount) { + messages.shift(); + } + + return messages; + }); + } + }, clientId); return unsubscribe; }, [showSyntaxError, maxMessageCount, clientId, resetOnPreviewRestart]); diff --git a/sandpack-react/src/contexts/utils/useClient.ts b/sandpack-react/src/contexts/utils/useClient.ts index f0ba7f3c7..a8539adab 100644 --- a/sandpack-react/src/contexts/utils/useClient.ts +++ b/sandpack-react/src/contexts/utils/useClient.ts @@ -6,8 +6,6 @@ import type { SandpackMessage, UnsubscribeFunction, SandpackClient, - SandpackMessageType, - ListenerOptions, } from "@codesandbox/sandpack-client"; import { loadSandpackClient, @@ -51,10 +49,9 @@ export interface UseClientOperations { clientPropsOverride?: ClientPropsOverride ) => Promise; registerReactDevTools: (value: ReactDevToolsMode) => void; - addListener: ( + addListener: ( listener: ListenerFunction, - clientId?: string, - opts?: ListenerOptions + clientId?: string ) => UnsubscribeFunction; dispatchMessage: (message: SandpackMessage, clientId?: string) => void; lazyAnchorRef: React.RefObject; @@ -64,10 +61,7 @@ export interface UseClientOperations { Record> >; queuedListenersRef: React.MutableRefObject< - Record< - string, - Record - > + Record> >; } @@ -112,10 +106,7 @@ export const useClient: UseClient = ( >({}); const unsubscribe = useRef<() => void | undefined>(); const queuedListeners = useRef< - Record< - string, - Record - > + Record> >({ global: {} }); const debounceHook = useRef(); const loadingScreenRegisteredRef = useRef(true); @@ -194,9 +185,8 @@ export const useClient: UseClient = ( */ if (queuedListeners.current[clientId]) { Object.keys(queuedListeners.current[clientId]).forEach((listenerId) => { - const { listener, opts } = - queuedListeners.current[clientId][listenerId]; - const unsubscribe = client.listen(listener, opts) as () => void; + const listener = queuedListeners.current[clientId][listenerId]; + const unsubscribe = client.listen(listener) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; }); @@ -209,8 +199,8 @@ export const useClient: UseClient = ( * Register global listeners */ const globalListeners = Object.entries(queuedListeners.current.global); - globalListeners.forEach(([listenerId, { listener, opts }]) => { - const unsubscribe = client.listen(listener, opts) as () => void; + globalListeners.forEach(([listenerId, listener]) => { + const unsubscribe = client.listen(listener) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; /** @@ -402,17 +392,13 @@ export const useClient: UseClient = ( } }; - const addListener = ( - listener: ListenerFunction, - clientId?: string, - opts?: ListenerOptions + const addListener = ( + listener: ListenerFunction, + clientId?: string ): UnsubscribeFunction => { if (clientId) { if (clients.current[clientId]) { - const unsubscribeListener = clients.current[clientId].listen( - listener, - opts - ); + const unsubscribeListener = clients.current[clientId].listen(listener); return unsubscribeListener; } else { @@ -427,10 +413,7 @@ export const useClient: UseClient = ( unsubscribeClientListeners.current[clientId] = unsubscribeClientListeners.current[clientId] || {}; - queuedListeners.current[clientId][listenerId] = { - listener: listener as any as ListenerFunction, - opts: opts as any as ListenerOptions, - }; + queuedListeners.current[clientId][listenerId] = listener; const unsubscribeListener = (): void => { if (queuedListeners.current[clientId][listenerId]) { @@ -454,10 +437,7 @@ export const useClient: UseClient = ( } else { // Push to the **global** queue const listenerId = generateRandomId(); - queuedListeners.current.global[listenerId] = { - listener: listener as any as ListenerFunction, - opts: opts as any as ListenerOptions, - }; + queuedListeners.current.global[listenerId] = listener; // Add to the current clients const clientsList = Object.values(clients.current); diff --git a/sandpack-react/src/types.ts b/sandpack-react/src/types.ts index aef7ee1ac..0510b3cb7 100644 --- a/sandpack-react/src/types.ts +++ b/sandpack-react/src/types.ts @@ -12,8 +12,6 @@ import type { UnsubscribeFunction, SandpackLogLevel, NpmRegistry, - SandpackMessageType, - ListenerOptions, } from "@codesandbox/sandpack-client"; import type React from "react"; @@ -519,12 +517,9 @@ export type SandpackClientDispatch = ( clientId?: string ) => void; -export type SandpackClientListen = < - Type extends SandpackMessageType = SandpackMessageType ->( - listener: ListenerFunction, - clientId?: string, - opts?: ListenerOptions +export type SandpackClientListen = ( + listener: ListenerFunction, + clientId?: string ) => UnsubscribeFunction; export type SandpackContext = SandpackState & { From 88115bdb375016ee36c28a86cbd00b99c572ad37 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 8 Sep 2023 15:06:33 +0100 Subject: [PATCH 4/6] introduce channel id --- .../src/clients/node/inject-scripts/index.ts | 1 + .../src/clients/node/taskManager.ts | 4 +-- sandpack-client/src/clients/static/index.ts | 17 ++++++---- .../components/Console/Console.stories.tsx | 33 +++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/sandpack-client/src/clients/node/inject-scripts/index.ts b/sandpack-client/src/clients/node/inject-scripts/index.ts index d08a6e9b9..83b7b8ac7 100644 --- a/sandpack-client/src/clients/node/inject-scripts/index.ts +++ b/sandpack-client/src/clients/node/inject-scripts/index.ts @@ -6,6 +6,7 @@ import { INJECT_MESSAGE_TYPE } from "@codesandbox/nodebox"; // get the bundled file, which contains all dependencies // @ts-ignore import consoleHook from "../../../inject-scripts/dist/consoleHook.js"; + import { setupHistoryListeners } from "./historyListener"; const scripts = [ diff --git a/sandpack-client/src/clients/node/taskManager.ts b/sandpack-client/src/clients/node/taskManager.ts index 77fd14f0f..93b1afe3b 100644 --- a/sandpack-client/src/clients/node/taskManager.ts +++ b/sandpack-client/src/clients/node/taskManager.ts @@ -40,14 +40,14 @@ type Token = | { type: TokenType.OR | TokenType.AND | TokenType.PIPE } | { type: TokenType.Command | TokenType.Argument | TokenType.String; - value: string; + value?: string; } | { type: TokenType.EnvVar; value: Record; }; -const operators = new Map([ +const operators = new Map([ ["&&", { type: TokenType.AND }], ["||", { type: TokenType.OR }], ["|", { type: TokenType.PIPE }], diff --git a/sandpack-client/src/clients/static/index.ts b/sandpack-client/src/clients/static/index.ts index ddde797a4..6746a46df 100644 --- a/sandpack-client/src/clients/static/index.ts +++ b/sandpack-client/src/clients/static/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import type { FilesMap } from "@codesandbox/nodebox"; import type { FileContent } from "static-browser-server"; import { PreviewController } from "static-browser-server"; @@ -8,17 +9,16 @@ import type { SandboxSetup, UnsubscribeFunction, } from "../.."; +// get the bundled file, which contains all dependencies +// @ts-ignore +import consoleHook from "../../inject-scripts/dist/consoleHook.js"; import { SandpackClient } from "../base"; import { EventEmitter } from "../event-emitter"; -import { fromBundlerFilesToFS } from "../node/client.utils"; +import { fromBundlerFilesToFS, generateRandomId } from "../node/client.utils"; import type { SandpackNodeMessage } from "../node/types"; import { insertHtmlAfterRegex, readBuffer, validateHtml } from "./utils"; -// get the bundled file, which contains all dependencies -// @ts-ignore -import consoleHook from "../../inject-scripts/dist/consoleHook.js"; - export class SandpackStatic extends SandpackClient { private emitter: EventEmitter; private previewController: PreviewController; @@ -56,6 +56,7 @@ export class SandpackStatic extends SandpackClient { ); content = this.injectScriptIntoHead(content, { script: consoleHook, + scope: { channelId: generateRandomId() }, }); } catch (err) { console.error("Runtime injection failed", err); @@ -151,7 +152,11 @@ export class SandpackStatic extends SandpackClient { private injectScriptIntoHead( content: FileContent, - opts: { script: string; scope?: Record } + opts: { + script: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + scope?: { channelId: string } & Record; + } ): FileContent { const { script, scope = {} } = opts; const scriptToInsert = ` diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx index 0d7631ff0..b31985234 100644 --- a/sandpack-react/src/components/Console/Console.stories.tsx +++ b/sandpack-react/src/components/Console/Console.stories.tsx @@ -235,3 +235,36 @@ export const MaxMessageCount = () => { ); }; + +export const StaticTemplate: React.FC = () => { + return ( + + + + + Parcel Sandbox + + + + + + +

Hello world

+ + + +`, + }} + options={{ showConsole: true }} + template="static" + /> + ); +}; + +export const NodeTemplate: React.FC = () => { + return ; +}; From 0e7012ab5b7f634411d10d965719f9a58b2e87e9 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 8 Sep 2023 15:40:00 +0100 Subject: [PATCH 5/6] console: support nodelist --- .../components/Console/Console.stories.tsx | 1 + .../src/components/Console/ConsoleList.tsx | 1 - .../components/Console/utils/constraints.ts | 1 + .../Console/utils/fromConsoleToString.test.ts | 67 +++++++++++++++++++ .../Console/utils/fromConsoleToString.ts | 29 ++++++++ 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx index b31985234..f9720895e 100644 --- a/sandpack-react/src/components/Console/Console.stories.tsx +++ b/sandpack-react/src/components/Console/Console.stories.tsx @@ -255,6 +255,7 @@ export const StaticTemplate: React.FC = () => {

Hello world

+ `, diff --git a/sandpack-react/src/components/Console/ConsoleList.tsx b/sandpack-react/src/components/Console/ConsoleList.tsx index 2761636e0..1b598a2ea 100644 --- a/sandpack-react/src/components/Console/ConsoleList.tsx +++ b/sandpack-react/src/components/Console/ConsoleList.tsx @@ -12,7 +12,6 @@ export const ConsoleList: React.FC<{ data: SandpackConsoleData }> = ({ data, }) => { const classNames = useClassNames(); - return ( <> {data.map(({ data, id, method }, logIndex, references) => { diff --git a/sandpack-react/src/components/Console/utils/constraints.ts b/sandpack-react/src/components/Console/utils/constraints.ts index b423e16ce..70b39c796 100644 --- a/sandpack-react/src/components/Console/utils/constraints.ts +++ b/sandpack-react/src/components/Console/utils/constraints.ts @@ -7,6 +7,7 @@ export const CLEAR_LOG = { }; export const TRANSFORMED_TYPE_KEY = "@t"; +export const TRANSFORMED_TYPE_KEY_ALTERNATE = "#@t"; export const CIRCULAR_REF_KEY = "@r"; export const MAX_LENGTH_STRING = 10000; diff --git a/sandpack-react/src/components/Console/utils/fromConsoleToString.test.ts b/sandpack-react/src/components/Console/utils/fromConsoleToString.test.ts index c20310c3e..b3336744b 100644 --- a/sandpack-react/src/components/Console/utils/fromConsoleToString.test.ts +++ b/sandpack-react/src/components/Console/utils/fromConsoleToString.test.ts @@ -40,6 +40,73 @@ const cases: Array<[Message, string]> = [ { constructor: { name: "CustomThing" } }, 'CustomThing { constructor: { name: "CustomThing" } }', ], + [ + { + "0": { + "#@t": "HTMLElement", + data: { + tagName: "button", + attributes: {}, + innerHTML: "Test", + }, + }, + "1": { + "#@t": "HTMLElement", + data: { + tagName: "button", + attributes: { + onclick: "console.log(document.querySelectorAll('button'))", + }, + innerHTML: "Log", + }, + }, + entries: { + "#@t": "Function", + data: { + name: "entries", + body: "", + proto: "Function", + }, + }, + keys: { + "#@t": "Function", + data: { + name: "keys", + body: "", + proto: "Function", + }, + }, + values: { + "#@t": "Function", + data: { + name: "values", + body: "", + proto: "Function", + }, + }, + forEach: { + "#@t": "Function", + data: { + name: "forEach", + body: "", + proto: "Function", + }, + }, + length: 2, + item: { + "#@t": "Function", + data: { + name: "item", + body: "", + proto: "Function", + }, + }, + constructor: { + name: "NodeList", + }, + }, + `NodeList(2)[,]`, + ], /** * Function diff --git a/sandpack-react/src/components/Console/utils/fromConsoleToString.ts b/sandpack-react/src/components/Console/utils/fromConsoleToString.ts index 7a8cbb73f..51f06b849 100644 --- a/sandpack-react/src/components/Console/utils/fromConsoleToString.ts +++ b/sandpack-react/src/components/Console/utils/fromConsoleToString.ts @@ -7,6 +7,7 @@ import { TRANSFORMED_TYPE_KEY, + TRANSFORMED_TYPE_KEY_ALTERNATE, MAX_NEST_LEVEL, MAX_KEYS, MAX_LENGTH_STRING, @@ -48,6 +49,25 @@ const formatSymbols = (message: Message): any => { const transform = transformers[type]; return transform(message.data); + } else if ( + typeof message == "object" && + TRANSFORMED_TYPE_KEY_ALTERNATE in message + ) { + const type = message[TRANSFORMED_TYPE_KEY_ALTERNATE] as TransformsTypes; + const transform = transformers[type]; + + return transform(message.data); + } else if ( + typeof message == "object" && + message.constructor?.name === "NodeList" + ) { + const NodeList = {}; + Object.entries(message).forEach(([key, value]) => { + // @ts-ignore + NodeList[key] = formatSymbols(value); + }); + + return NodeList; } return message; @@ -163,6 +183,15 @@ export const fromConsoleToString = ( return fromConsoleToString(newMessage, references, level + 1); } + if (output.constructor?.name === "NodeList") { + const length = output.length; + const nodes = new Array(length).fill(null).map((_, index) => { + return fromConsoleToString(output[index], references); + }); + + return `NodeList(${output.length})[${nodes}]`; + } + return objectToString(output, references, level + 1); } } catch { From f726fe07909878370f98f466a3b6f4c9c728b3ff Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 8 Sep 2023 15:41:58 +0100 Subject: [PATCH 6/6] story --- .../components/Console/Console.stories.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx index f9720895e..6485d7b2a 100644 --- a/sandpack-react/src/components/Console/Console.stories.tsx +++ b/sandpack-react/src/components/Console/Console.stories.tsx @@ -269,3 +269,23 @@ export const StaticTemplate: React.FC = () => { export const NodeTemplate: React.FC = () => { return ; }; + +export const ReactTemplate: React.FC = () => { + return ( + + + ) +}`, + }} + template="react" + /> + ); +};