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..83b7b8ac7 100644 --- a/sandpack-client/src/clients/node/inject-scripts/index.ts +++ b/sandpack-client/src/clients/node/inject-scripts/index.ts @@ -5,7 +5,8 @@ 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/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 3dfcfac67..6038a8a55 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,9 +9,12 @@ 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"; @@ -52,6 +56,10 @@ export class SandpackStatic extends SandpackClient { content, options.externalResources ); + content = this.injectScriptIntoHead(content, { + script: consoleHook, + scope: { channelId: generateRandomId() }, + }); } catch (err) { console.error("Runtime injection failed", err); } @@ -82,6 +90,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(); } @@ -139,6 +152,25 @@ export class SandpackStatic extends SandpackClient { return this.injectContentIntoHead(content, tagsToInsert); } + private injectScriptIntoHead( + content: FileContent, + opts: { + script: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + scope?: { channelId: string } & Record; + } + ): FileContent { + const { script, scope = {} } = opts; + const scriptToInsert = ` + + `.trim(); + + return this.injectContentIntoHead(content, scriptToInsert); + } + public updateSandbox( setup = this.sandboxSetup, _isInitializationCompile?: boolean @@ -172,6 +204,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 */ @@ -193,5 +240,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 diff --git a/sandpack-react/src/components/Console/Console.stories.tsx b/sandpack-react/src/components/Console/Console.stories.tsx index 0d7631ff0..6485d7b2a 100644 --- a/sandpack-react/src/components/Console/Console.stories.tsx +++ b/sandpack-react/src/components/Console/Console.stories.tsx @@ -235,3 +235,57 @@ export const MaxMessageCount = () => { ); }; + +export const StaticTemplate: React.FC = () => { + return ( + + + + + Parcel Sandbox + + + + + + +

Hello world

+ + + + +`, + }} + options={{ showConsole: true }} + template="static" + /> + ); +}; + +export const NodeTemplate: React.FC = () => { + return ; +}; + +export const ReactTemplate: React.FC = () => { + return ( + + + ) +}`, + }} + template="react" + /> + ); +}; 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 {