diff --git a/sandpack-client/src/clients/runtime/index.ts b/sandpack-client/src/clients/runtime/index.ts index 44365ad7e..d6eccbf2d 100644 --- a/sandpack-client/src/clients/runtime/index.ts +++ b/sandpack-client/src/clients/runtime/index.ts @@ -19,6 +19,7 @@ import { extractErrorDetails, createPackageJSON, addPackageJSONIfNeeded, + codeToString, } from "../../utils"; import { SandpackClient } from "../base"; @@ -199,7 +200,7 @@ export class SandpackRuntime extends SandpackClient { ) ); try { - packageJSON = JSON.parse(files["/package.json"].code); + packageJSON = JSON.parse(codeToString(files["/package.json"].code)); } catch (e) { console.error( createError( diff --git a/sandpack-client/src/types.ts b/sandpack-client/src/types.ts index 757cd77f3..bca548e5d 100644 --- a/sandpack-client/src/types.ts +++ b/sandpack-client/src/types.ts @@ -80,7 +80,7 @@ export interface SandboxSetup { } export interface SandpackBundlerFile { - code: string; + code: string | Uint8Array; hidden?: boolean; active?: boolean; readOnly?: boolean; diff --git a/sandpack-client/src/utils.test.ts b/sandpack-client/src/utils.test.ts index 1429a7c23..d76fa79b1 100644 --- a/sandpack-client/src/utils.test.ts +++ b/sandpack-client/src/utils.test.ts @@ -1,4 +1,4 @@ -import { addPackageJSONIfNeeded, normalizePath } from "./utils"; +import { addPackageJSONIfNeeded, codeToString, normalizePath } from "./utils"; const files = { "/package.json": { @@ -15,7 +15,9 @@ describe(addPackageJSONIfNeeded, () => { test("it merges the package.json - dependencies", () => { const output = addPackageJSONIfNeeded(files, { foo: "*" }); - expect(JSON.parse(output["/package.json"].code).dependencies).toEqual({ + expect( + JSON.parse(codeToString(output["/package.json"].code)).dependencies + ).toEqual({ baz: "*", foo: "*", }); @@ -24,7 +26,9 @@ describe(addPackageJSONIfNeeded, () => { test("it merges the package.json - dev-dependencies", () => { const output = addPackageJSONIfNeeded(files, undefined, { foo: "*" }); - expect(JSON.parse(output["/package.json"].code).devDependencies).toEqual({ + expect( + JSON.parse(codeToString(output["/package.json"].code)).devDependencies + ).toEqual({ baz: "*", foo: "*", }); @@ -38,7 +42,7 @@ describe(addPackageJSONIfNeeded, () => { "new-entry.js" ); - expect(JSON.parse(output["/package.json"].code).main).toEqual( + expect(JSON.parse(codeToString(output["/package.json"].code)).main).toEqual( "new-entry.js" ); }); @@ -59,7 +63,7 @@ describe(addPackageJSONIfNeeded, () => { "new-entry.js" ); - expect(JSON.parse(output["/package.json"].code).main).toEqual( + expect(JSON.parse(codeToString(output["/package.json"].code)).main).toEqual( "new-entry.js" ); }); @@ -91,7 +95,7 @@ describe(addPackageJSONIfNeeded, () => { "new-entry.ts" ); - expect(JSON.parse(output["/package.json"].code)).toEqual({ + expect(JSON.parse(codeToString(output["/package.json"].code))).toEqual({ main: "new-entry.ts", dependencies: { baz: "*", foo: "*" }, devDependencies: { baz: "*", foo: "*" }, diff --git a/sandpack-client/src/utils.ts b/sandpack-client/src/utils.ts index 5cb865d87..972283672 100644 --- a/sandpack-client/src/utils.ts +++ b/sandpack-client/src/utils.ts @@ -17,6 +17,15 @@ export function nullthrows(value?: T | null, err = "Value is nullish"): T { return value; } +export function codeToString(code: string | Uint8Array): string { + if (typeof code === "string") { + return code; + } else { + const decoder = new TextDecoder(); + return decoder.decode(code); + } +} + const DEPENDENCY_ERROR_MESSAGE = `"dependencies" was not specified - provide either a package.json or a "dependencies" value`; const ENTRY_ERROR_MESSAGE = `"entry" was not specified - provide either a package.json with the "main" field or an "entry" value`; @@ -65,7 +74,7 @@ export function addPackageJSONIfNeeded( * Merge package json with custom setup */ if (packageJsonFile) { - const packageJsonContent = JSON.parse(packageJsonFile.code); + const packageJsonContent = JSON.parse(codeToString(packageJsonFile.code)); nullthrows( !(!dependencies && !packageJsonContent.dependencies), diff --git a/sandpack-react/src/components/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx b/sandpack-react/src/components/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx index 62c2a50d0..371a8c70a 100644 --- a/sandpack-react/src/components/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx +++ b/sandpack-react/src/components/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx @@ -5,6 +5,7 @@ import * as React from "react"; import { useSandpack } from "../../../hooks/useSandpack"; import type { SandboxEnvironment } from "../../../types"; +import { codeToString } from "../../../utils/stringUtils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const getParameters = (parameters: Record): string => @@ -28,10 +29,11 @@ const getFileParameters = ( >; const normalizedFiles = Object.keys(files).reduce((prev, next) => { + const code = files[next].code; const fileName = next.replace("/", ""); const value = { - content: files[next].code, - isBinary: false, + content: codeToString(code), + isBinary: code instanceof Uint8Array, }; return { ...prev, [fileName]: value }; diff --git a/sandpack-react/src/hooks/useActiveCode.ts b/sandpack-react/src/hooks/useActiveCode.ts index 2afdb291a..36ae032d6 100644 --- a/sandpack-react/src/hooks/useActiveCode.ts +++ b/sandpack-react/src/hooks/useActiveCode.ts @@ -1,3 +1,5 @@ +import { codeToString } from "../utils/stringUtils"; + import { useSandpack } from "./useSandpack"; /** @@ -14,7 +16,7 @@ export const useActiveCode = (): { const { sandpack } = useSandpack(); return { - code: sandpack.files[sandpack.activeFile]?.code, + code: codeToString(sandpack.files[sandpack.activeFile]?.code), readOnly: sandpack.files[sandpack.activeFile]?.readOnly ?? false, updateCode: sandpack.updateCurrentFile, }; diff --git a/sandpack-react/src/types.ts b/sandpack-react/src/types.ts index ee734d268..cb686b283 100644 --- a/sandpack-react/src/types.ts +++ b/sandpack-react/src/types.ts @@ -236,7 +236,7 @@ export type SandpackPredefinedTemplate = keyof typeof SANDBOX_TEMPLATES; * @category Setup */ export interface SandpackFile { - code: string; + code: string | Uint8Array; hidden?: boolean; active?: boolean; readOnly?: boolean; @@ -591,7 +591,7 @@ export interface SandboxTemplate { environment: SandboxEnvironment; } -export type SandpackFiles = Record; +export type SandpackFiles = Record; /** * Custom properties to be used in the SandpackCodeEditor component, diff --git a/sandpack-react/src/utils/sandpackUtils.ts b/sandpack-react/src/utils/sandpackUtils.ts index 0f39fdf1b..1229f0e18 100644 --- a/sandpack-react/src/utils/sandpackUtils.ts +++ b/sandpack-react/src/utils/sandpackUtils.ts @@ -53,7 +53,7 @@ export const getSandpackStateFromProps = ( // extract open and active files from the custom input files Object.keys(normalizedFilesPath).forEach((filePath) => { const file = normalizedFilesPath[filePath]; - if (typeof file === "string") { + if (typeof file === "string" || file instanceof Uint8Array) { visibleFiles.push(filePath); return; } diff --git a/sandpack-react/src/utils/stringUtils.ts b/sandpack-react/src/utils/stringUtils.ts index 285b7c5ef..b8807a2cc 100644 --- a/sandpack-react/src/utils/stringUtils.ts +++ b/sandpack-react/src/utils/stringUtils.ts @@ -53,6 +53,15 @@ export const calculateNearestUniquePath = ( return resultPathParts.join("/"); }; +export function codeToString(code: string | Uint8Array): string { + if (typeof code === "string") { + return code; + } else { + const decoder = new TextDecoder(); + return decoder.decode(code); + } +} + export const hexToRGB = ( hex: string ): { red: number; green: number; blue: number } => {