Skip to content

Commit 9f70efc

Browse files
committed
feat: Accept Uint8Array as file code alongside string
- adjust SandpackBundlerFile and SandpackFile types to accept Uin8Array - create `codeToString` utils to convert Uint8Array into utf-8 string using `TextDecoder`
1 parent b7386d2 commit 9f70efc

File tree

9 files changed

+42
-15
lines changed

9 files changed

+42
-15
lines changed

sandpack-client/src/clients/runtime/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
extractErrorDetails,
2020
createPackageJSON,
2121
addPackageJSONIfNeeded,
22+
codeToString,
2223
} from "../../utils";
2324
import { SandpackClient } from "../base";
2425

@@ -199,7 +200,7 @@ export class SandpackRuntime extends SandpackClient {
199200
)
200201
);
201202
try {
202-
packageJSON = JSON.parse(files["/package.json"].code);
203+
packageJSON = JSON.parse(codeToString(files["/package.json"].code));
203204
} catch (e) {
204205
console.error(
205206
createError(

sandpack-client/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export interface SandboxSetup {
8080
}
8181

8282
export interface SandpackBundlerFile {
83-
code: string;
83+
code: string | Uint8Array;
8484
hidden?: boolean;
8585
active?: boolean;
8686
readOnly?: boolean;

sandpack-client/src/utils.test.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addPackageJSONIfNeeded, normalizePath } from "./utils";
1+
import { addPackageJSONIfNeeded, codeToString, normalizePath } from "./utils";
22

33
const files = {
44
"/package.json": {
@@ -15,7 +15,9 @@ describe(addPackageJSONIfNeeded, () => {
1515
test("it merges the package.json - dependencies", () => {
1616
const output = addPackageJSONIfNeeded(files, { foo: "*" });
1717

18-
expect(JSON.parse(output["/package.json"].code).dependencies).toEqual({
18+
expect(
19+
JSON.parse(codeToString(output["/package.json"].code)).dependencies
20+
).toEqual({
1921
baz: "*",
2022
foo: "*",
2123
});
@@ -24,7 +26,9 @@ describe(addPackageJSONIfNeeded, () => {
2426
test("it merges the package.json - dev-dependencies", () => {
2527
const output = addPackageJSONIfNeeded(files, undefined, { foo: "*" });
2628

27-
expect(JSON.parse(output["/package.json"].code).devDependencies).toEqual({
29+
expect(
30+
JSON.parse(codeToString(output["/package.json"].code)).devDependencies
31+
).toEqual({
2832
baz: "*",
2933
foo: "*",
3034
});
@@ -38,7 +42,7 @@ describe(addPackageJSONIfNeeded, () => {
3842
"new-entry.js"
3943
);
4044

41-
expect(JSON.parse(output["/package.json"].code).main).toEqual(
45+
expect(JSON.parse(codeToString(output["/package.json"].code)).main).toEqual(
4246
"new-entry.js"
4347
);
4448
});
@@ -59,7 +63,7 @@ describe(addPackageJSONIfNeeded, () => {
5963
"new-entry.js"
6064
);
6165

62-
expect(JSON.parse(output["/package.json"].code).main).toEqual(
66+
expect(JSON.parse(codeToString(output["/package.json"].code)).main).toEqual(
6367
"new-entry.js"
6468
);
6569
});
@@ -91,7 +95,7 @@ describe(addPackageJSONIfNeeded, () => {
9195
"new-entry.ts"
9296
);
9397

94-
expect(JSON.parse(output["/package.json"].code)).toEqual({
98+
expect(JSON.parse(codeToString(output["/package.json"].code))).toEqual({
9599
main: "new-entry.ts",
96100
dependencies: { baz: "*", foo: "*" },
97101
devDependencies: { baz: "*", foo: "*" },

sandpack-client/src/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ export function nullthrows<T>(value?: T | null, err = "Value is nullish"): T {
1717
return value;
1818
}
1919

20+
export function codeToString(code: string | Uint8Array): string {
21+
if (typeof code === "string") {
22+
return code;
23+
} else {
24+
const decoder = new TextDecoder();
25+
return decoder.decode(code);
26+
}
27+
}
28+
2029
const DEPENDENCY_ERROR_MESSAGE = `"dependencies" was not specified - provide either a package.json or a "dependencies" value`;
2130
const ENTRY_ERROR_MESSAGE = `"entry" was not specified - provide either a package.json with the "main" field or an "entry" value`;
2231

@@ -65,7 +74,7 @@ export function addPackageJSONIfNeeded(
6574
* Merge package json with custom setup
6675
*/
6776
if (packageJsonFile) {
68-
const packageJsonContent = JSON.parse(packageJsonFile.code);
77+
const packageJsonContent = JSON.parse(codeToString(packageJsonFile.code));
6978

7079
nullthrows(
7180
!(!dependencies && !packageJsonContent.dependencies),

sandpack-react/src/components/common/OpenInCodeSandboxButton/UnstyledOpenInCodeSandboxButton.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as React from "react";
55

66
import { useSandpack } from "../../../hooks/useSandpack";
77
import type { SandboxEnvironment } from "../../../types";
8+
import { codeToString } from "../../../utils/stringUtils";
89

910
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1011
const getParameters = (parameters: Record<string, any>): string =>
@@ -28,10 +29,11 @@ const getFileParameters = (
2829
>;
2930

3031
const normalizedFiles = Object.keys(files).reduce((prev, next) => {
32+
const code = files[next].code;
3133
const fileName = next.replace("/", "");
3234
const value = {
33-
content: files[next].code,
34-
isBinary: false,
35+
content: codeToString(code),
36+
isBinary: code instanceof Uint8Array,
3537
};
3638

3739
return { ...prev, [fileName]: value };

sandpack-react/src/hooks/useActiveCode.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { codeToString } from "../utils/stringUtils";
2+
13
import { useSandpack } from "./useSandpack";
24

35
/**
@@ -14,7 +16,7 @@ export const useActiveCode = (): {
1416
const { sandpack } = useSandpack();
1517

1618
return {
17-
code: sandpack.files[sandpack.activeFile]?.code,
19+
code: codeToString(sandpack.files[sandpack.activeFile]?.code),
1820
readOnly: sandpack.files[sandpack.activeFile]?.readOnly ?? false,
1921
updateCode: sandpack.updateCurrentFile,
2022
};

sandpack-react/src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ export type SandpackPredefinedTemplate = keyof typeof SANDBOX_TEMPLATES;
236236
* @category Setup
237237
*/
238238
export interface SandpackFile {
239-
code: string;
239+
code: string | Uint8Array;
240240
hidden?: boolean;
241241
active?: boolean;
242242
readOnly?: boolean;
@@ -591,7 +591,7 @@ export interface SandboxTemplate {
591591
environment: SandboxEnvironment;
592592
}
593593

594-
export type SandpackFiles = Record<string, string | SandpackFile>;
594+
export type SandpackFiles = Record<string, string | Uint8Array | SandpackFile>;
595595

596596
/**
597597
* Custom properties to be used in the SandpackCodeEditor component,

sandpack-react/src/utils/sandpackUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const getSandpackStateFromProps = (
5353
// extract open and active files from the custom input files
5454
Object.keys(normalizedFilesPath).forEach((filePath) => {
5555
const file = normalizedFilesPath[filePath];
56-
if (typeof file === "string") {
56+
if (typeof file === "string" || file instanceof Uint8Array) {
5757
visibleFiles.push(filePath);
5858
return;
5959
}

sandpack-react/src/utils/stringUtils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ export const calculateNearestUniquePath = (
5353
return resultPathParts.join("/");
5454
};
5555

56+
export function codeToString(code: string | Uint8Array): string {
57+
if (typeof code === "string") {
58+
return code;
59+
} else {
60+
const decoder = new TextDecoder();
61+
return decoder.decode(code);
62+
}
63+
}
64+
5665
export const hexToRGB = (
5766
hex: string
5867
): { red: number; green: number; blue: number } => {

0 commit comments

Comments
 (0)