UNPKG

@tapcart/app-studio-utils

Version:

A utility library for Tapcart's App Studio

162 lines 8.03 kB
"use strict"; "use client"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.useAppStudioComponents = exports.AppStudioComponentsProvider = void 0; const jsx_runtime_1 = require("react/jsx-runtime"); const react_1 = __importStar(require("react")); const elements_1 = require("../elements"); const appStudioComponentImportRegex = /import\s+{\s*([^}]+)\s*}\s+from\s+["']@tapcart\/app-studio-components["']/; const AppStudioComponentsContext = react_1.default.createContext({ appStudioComponents: {}, setComponentScope: () => { }, isReady: false, }); const AppStudioComponentsProvider = ({ children, componentInputs, scopeInput, }) => { // Component scope should be set once and then not updated again unless the page state is changed. // We expose a function for updating the scope manually if needed. (Ex: Server scope vs client scope and we need client scope here) const [componentScope, setComponentScope] = (0, react_1.useState)(null); const [scopeOverrides, setScopeOverrides] = (0, react_1.useState)({}); const [isPending, startTransition] = (0, react_1.useTransition)(); const scopeHasChanges = (0, react_1.useCallback)((oldScope, newScope) => { // Remove page state from comparison // Page state is static except for the search params - search params are delivered through useSearchParams hook // Do not need to regenerate components if only search params have changed. Also provides better user experience // Copy objects first to prevent unintended mutations of the original scope objects const scope1 = Object.assign({}, oldScope); const scope2 = Object.assign({}, newScope); delete scope1.pageState; delete scope2.pageState; // JSON.stringify will remove undefined values and functions // Compare the number of keys to determine if the scope has changed if (Object.keys(scope1).length !== Object.keys(scope2).length) { return true; } return JSON.stringify(scope1) !== JSON.stringify(scope2); }, []); (0, react_1.useEffect)(() => { const currentScope = componentScope || scopeInput; if (!currentScope) return; let newScope = Object.assign({}, currentScope); // Remove block specific properties from scope overrides as this is not a block delete newScope.blockConfig; delete newScope.blockId; // TODO: Implement manifest options newScope.componentConfig = {}; // Check if the scope has changed if (!scopeHasChanges(scopeOverrides, newScope)) return; startTransition(() => { setScopeOverrides(newScope); }); }, [componentScope, scopeInput]); // Responsible for instantiating a component and its dependencies const instantiateComponent = (0, react_1.useCallback)((component, componentCache) => { // Components can reference each other. Find app studio component dependencies in component code const importMatch = component.code.match(appStudioComponentImportRegex); // If there is an import, there are other custom component dependencies that we need to load if (importMatch === null || importMatch === void 0 ? void 0 : importMatch.length) { // Extract the component dependencies from the import statement const strippedImport = importMatch[0] .replace(/import {|}|"|;|from|@tapcart\/app-studio-components/g, "") .trim(); const componentDependencies = strippedImport .split(",") .map((dep) => dep.trim()); // For each dependency, create that component and store it in the cache for (const dependency of componentDependencies) { if (!dependency || componentCache[dependency]) continue; // Find dependency in componentInputs const dependencyComponent = componentInputs[dependency]; if (!dependencyComponent) { console.warn(`App studio component "${dependency}" not found in componentInputs`); continue; } const dependencyComponentInstance = instantiateComponent(dependencyComponent, componentCache); if (!dependencyComponentInstance) continue; componentCache[dependency] = dependencyComponentInstance; } } // With dependencies loaded, instantiate the component const AppStudioComponent = (0, elements_1.generateElement)({ code: component.transpiledCode, scopeOverrides, appStudioComponents: componentCache, }); if (!AppStudioComponent) { console.error(`Failed to instantiate app studio component ${component.key}`); return null; } // Return function that renders the component. Do this so we can inject our scope as well as block related props. const Component = (props) => ((0, jsx_runtime_1.jsx)(AppStudioComponent, Object.assign({}, scopeOverrides, props))); Component.displayName = "AppStudioComponent"; return Component; }, [scopeOverrides]); // Full output of instantiated components const appStudioComponents = (0, react_1.useMemo)(() => { if (isPending) return {}; let componentCache = {}; // Turn app studio component inputs into a map of component name to component function const components = Object.keys(componentInputs).reduce((acc, key) => { if (!componentInputs[key]) return acc; const component = instantiateComponent(componentInputs[key], componentCache); if (!component) return acc; acc[key] = component; return acc; }, {}); return components; }, [scopeOverrides, componentInputs]); return ((0, jsx_runtime_1.jsx)(AppStudioComponentsContext.Provider, { value: { appStudioComponents, setComponentScope, isReady: !isPending, }, children: children })); }; exports.AppStudioComponentsProvider = AppStudioComponentsProvider; const useAppStudioComponents = () => { const context = react_1.default.useContext(AppStudioComponentsContext); if (context === undefined) { throw new Error("useAppStudioComponents must be used within a AppStudioComponentsProvider"); } return context; }; exports.useAppStudioComponents = useAppStudioComponents; //# sourceMappingURL=index.js.map