From c924df231add11918d20eced1793dc2c2729fa61 Mon Sep 17 00:00:00 2001 From: Danilo Woznica Date: Fri, 30 Sep 2022 12:16:32 +0000 Subject: [PATCH] Update 3 files --- .../src/contexts/sandpackContext.tsx | 24 +++- .../src/utils/sandpackUtils.test.ts | 21 ---- sandpack-react/src/utils/sandpackUtils.ts | 110 ++++++++++-------- 3 files changed, 79 insertions(+), 76 deletions(-) diff --git a/sandpack-react/src/contexts/sandpackContext.tsx b/sandpack-react/src/contexts/sandpackContext.tsx index 8f0665d34..e6e9b4dbe 100644 --- a/sandpack-react/src/contexts/sandpackContext.tsx +++ b/sandpack-react/src/contexts/sandpackContext.tsx @@ -68,7 +68,11 @@ export class SandpackProviderClass extends React.PureComponent< super(props); const { activeFile, visibleFiles, files, environment } = - getSandpackStateFromProps(props); + getSandpackStateFromProps({ + template: props.template, + files: props.files, + customSetup: props.customSetup, + }); this.state = { files, @@ -272,7 +276,11 @@ export class SandpackProviderClass extends React.PureComponent< * Custom setup derived from props */ const { activeFile, visibleFiles, files, environment } = - getSandpackStateFromProps(this.props); + getSandpackStateFromProps({ + template: this.props.template, + files: this.props.files, + customSetup: this.props.customSetup, + }); /** * What the changes on the customSetup props @@ -600,7 +608,11 @@ export class SandpackProviderClass extends React.PureComponent< }; resetFile = (path: string): void => { - const { files } = getSandpackStateFromProps(this.props); + const { files } = getSandpackStateFromProps({ + template: this.props.template, + files: this.props.files, + customSetup: this.props.customSetup, + }); this.setState( (prevState) => ({ @@ -611,7 +623,11 @@ export class SandpackProviderClass extends React.PureComponent< }; resetAllFiles = (): void => { - const { files } = getSandpackStateFromProps(this.props); + const { files } = getSandpackStateFromProps({ + template: this.props.template, + files: this.props.files, + customSetup: this.props.customSetup, + }); this.setState({ files }, this.updateClients); }; diff --git a/sandpack-react/src/utils/sandpackUtils.test.ts b/sandpack-react/src/utils/sandpackUtils.test.ts index 84cbf68ce..f47a49c9e 100644 --- a/sandpack-react/src/utils/sandpackUtils.test.ts +++ b/sandpack-react/src/utils/sandpackUtils.test.ts @@ -2,7 +2,6 @@ import { REACT_TEMPLATE } from "../templates/react"; import { getSandpackStateFromProps, - createSetupFromUserInput, resolveFile, convertedFilesToBundlerFiles, } from "./sandpackUtils"; @@ -419,26 +418,6 @@ describe(getSandpackStateFromProps, () => { }); }); -describe(createSetupFromUserInput, () => { - test("convert `files` to a key/value format", () => { - const setup = createSetupFromUserInput({ files: { "App.js": "" } }); - - expect(setup).toStrictEqual({ files: { "App.js": { code: "" } } }); - }); - - test("supports custom properties", () => { - const setup = createSetupFromUserInput({ - files: { "App.js": "" }, - customSetup: { environment: "create-react-app" }, - }); - - expect(setup).toStrictEqual({ - environment: "create-react-app", - files: { "App.js": { code: "" } }, - }); - }); -}); - describe(convertedFilesToBundlerFiles, () => { it("converts regular files to bundler files", () => { expect(convertedFilesToBundlerFiles({ name: "code" })).toEqual({ diff --git a/sandpack-react/src/utils/sandpackUtils.ts b/sandpack-react/src/utils/sandpackUtils.ts index cf6a23f01..47dc6514a 100644 --- a/sandpack-react/src/utils/sandpackUtils.ts +++ b/sandpack-react/src/utils/sandpackUtils.ts @@ -24,13 +24,19 @@ export interface SandpackContextInfo { environment: SandboxEnvironment; } +/** + * Creates a standard sandpack state given the setup, + * options, and files props. Using this function is + * the reliable way to ensure a consistent and predictable + * sandpack-content throughout application + */ export const getSandpackStateFromProps = ( props: SandpackProviderProps ): SandpackContextInfo => { const normalizedFilesPath = normalizePath(props.files); // Merge predefined template with custom setup - const projectSetup = getSetup({ + const projectSetup = combineTemplateFilesToSetup({ template: props.template, customSetup: props.customSetup, files: normalizedFilesPath, @@ -109,20 +115,30 @@ export const getSandpackStateFromProps = ( }; }; +/** + * Given a file tree and a file, it uses a couple of rules + * to tweak the filename to match with one of the inside of file tree + * + * - Adds the leading slash; + * - Tries to find the same filename with different extensions (js only); + * - Returns `null` if it doesn't satisfy any rule + */ export const resolveFile = ( path: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - files: Record -): string | undefined => { + files: SandpackFiles +): string | null => { const normalizedFilesPath = normalizePath(files); const normalizedPath = normalizePath(path); - if (normalizedPath in normalizedFilesPath) return normalizedPath; - - if (!path) return undefined; + if (normalizedPath in normalizedFilesPath) { + return normalizedPath; + } - let resolvedPath = undefined; + if (!path) { + return null; + } + let resolvedPath = null; let index = 0; const strategies = [".js", ".jsx", ".ts", ".tsx"]; @@ -142,9 +158,10 @@ export const resolveFile = ( /** * The template is predefined (eg: react, vue, vanilla) - * The setup can overwrite anything from the template (eg: files, dependencies, environment, etc.) + * The setup can overwrite anything from the template + * (eg: files, dependencies, environment, etc.) */ -export const getSetup = ({ +const combineTemplateFilesToSetup = ({ files, template, customSetup, @@ -153,26 +170,23 @@ export const getSetup = ({ template?: SandpackPredefinedTemplate; customSetup?: SandpackSetup; }): SandboxTemplate => { - /** - * The input setup might have files in the simple form Record - * so we convert them to the sandbox template format - */ - const setup = createSetupFromUserInput({ customSetup, files }); - if (!template) { // If not input, default to vanilla - if (!setup) { + if (!customSetup) { return SANDBOX_TEMPLATES.vanilla as SandboxTemplate; } - if (!setup.files || Object.keys(setup.files).length === 0) { + if (!files || Object.keys(files).length === 0) { throw new Error( `[sandpack-react]: without a template, you must pass at least one file` ); } // If not template specified, use the setup entirely - return setup as SandboxTemplate; + return { + ...customSetup, + files: convertedFilesToBundlerFiles(files), + } as SandboxTemplate; } const baseTemplate = SANDBOX_TEMPLATES[template] as SandboxTemplate; @@ -182,31 +196,48 @@ export const getSetup = ({ ); } - // If no setup, the template is used entirely - if (!setup) { + // If no setup and not files, the template is used entirely + if (!customSetup && !files) { return baseTemplate; } // Merge the setup on top of the template return { - files: { ...baseTemplate.files, ...setup.files }, + /** + * The input setup might have files in the simple form Record + * so we convert them to the sandbox template format + */ + files: convertedFilesToBundlerFiles({ ...baseTemplate.files, ...files }), + /** + * Merge template dependencies and user custom dependencies. + * As a rule, the custom dependencies must overwrite the template ones. + */ dependencies: { ...baseTemplate.dependencies, - ...setup.dependencies, + ...customSetup?.dependencies, }, devDependencies: { ...baseTemplate.devDependencies, - ...setup.devDependencies, + ...customSetup?.devDependencies, }, - entry: normalizePath(setup.entry || baseTemplate.entry), - main: setup.main || baseTemplate.main, - environment: setup.environment || baseTemplate.environment, + entry: normalizePath(customSetup?.entry || baseTemplate.entry), + main: baseTemplate.main, + environment: customSetup?.environment || baseTemplate.environment, } as SandboxTemplate; }; +/** + * Transform an regular object, which contain files to + * an object that sandpack-client can understand + * + * From: Record + * To: Record + */ export const convertedFilesToBundlerFiles = ( - files: SandpackFiles + files?: SandpackFiles ): SandpackBundlerFiles => { + if (!files) return {}; + return Object.keys(files).reduce((acc: SandpackBundlerFiles, key) => { if (typeof files[key] === "string") { acc[key] = { code: files[key] as string }; @@ -217,26 +248,3 @@ export const convertedFilesToBundlerFiles = ( return acc; }, {}); }; - -export const createSetupFromUserInput = ({ - files, - customSetup, -}: { - files?: SandpackFiles; - customSetup?: SandpackSetup; -}): Partial | null => { - if (!files && !customSetup) { - return null; - } - - if (!files) { - return customSetup as Partial; - } - - const convertedFiles = convertedFilesToBundlerFiles(files); - - return { - ...customSetup, - files: convertedFiles, - }; -};